Refactor: Move tests to separate files
This commit is contained in:
parent
33fde0cbf4
commit
457acc6344
1810
src/tests.rs
1810
src/tests.rs
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,189 @@
|
|||
use crate::{analysis::blocked_in, *};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn test_stage_1_analysis() {
|
||||
// Test stage 1 analysis
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 1 connection, 0 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 0 connections, 0 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
0,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 0 connections, 1 negative report
|
||||
// (exceeds scaled threshold)
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 1 connection, 1 negative report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 8 connections, 2 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 8 connections, 3 negative reports
|
||||
// (exceeds scaled threshold)
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
3,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// 24 connections, 5 negative reports
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
5,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// 24 connections, 6 negative reports
|
||||
// (exceeds max threshold)
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
6,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,497 @@
|
|||
use crate::{analysis::blocked_in, *};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stage_3_analysis() {
|
||||
// Test workaround when computed covariance matrix is not positive definite
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for _ in 1..30 {
|
||||
// 9-16 connections, 1 negative report, 13 positive reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
16,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
13,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 16 connections, 1 negative report, 12 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
16,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report, 100 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
100,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 40 connections, 12 negative reports, 40 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
40,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
40,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// 9-32 connections, 0-3 negative reports, 16-20 positive reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 2),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
16 + i % 5,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 24 connections, 2 negative reports, 17 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
17,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report, 100 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
100,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 40 connections, 12 negative reports, 40 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
40,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
40,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// 9-32 connections, 0-3 negative reports, 16-20 positive reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 2),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
16 + i % 5,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 24 connections, 2 negative reports, 17 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
17,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report, 85 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
85,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 800 connections, 12 negative reports, 750 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
800,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
750,
|
||||
);
|
||||
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// The censor artificially inflated bridge stats to prevent detection.
|
||||
// Ensure we still detect the censorship from negative reports.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// 9-32 connections, 0-3 negative reports, 16-20 positive reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 2),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
16 + i % 5,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 24 connections, 2 negative reports, 17 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
17,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report, 100 positive reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
100,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 24 connections, 1 negative report, 1 positive report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::PositiveReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,374 @@
|
|||
use crate::{analysis::blocked_in, *};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stage_2_analysis() {
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// 9-32 connections, 0-3 negative reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 2),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 24 connections, 2 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 40 connections, 12 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
40,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Test workaround when computed covariance matrix is not positive definite
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for _ in 1..30 {
|
||||
// 1-8 connections, 1 negative report each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 8 connections, 1 negative report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 800 connections, 12 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
800,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
12,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// The censor artificially inflated bridge stats to prevent detection.
|
||||
// Ensure we still detect the censorship from negative reports.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// 9-32 connections, 0-3 negative reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 2),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data similar to previous days:
|
||||
// 24 connections, 2 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
24,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
|
||||
// Should not be blocked because we have similar data.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 104 connections, 1 negative report
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
104,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
// This should not be blocked even though it's very different because
|
||||
// it's different in the good direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
// Data different from previous days:
|
||||
// 0 connections, 0 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
0,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
0,
|
||||
);
|
||||
//blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut date = get_date();
|
||||
|
||||
// New bridge info
|
||||
let mut bridge_info = BridgeInfo::new([0; 20], &String::default());
|
||||
|
||||
bridge_info
|
||||
.info_by_country
|
||||
.insert("ru".to_string(), BridgeCountryInfo::new(date));
|
||||
let analyzer = analysis::NormalAnalyzer::new(5, 0.25);
|
||||
let confidence = 0.95;
|
||||
|
||||
let mut blocking_countries = HashSet::<String>::new();
|
||||
|
||||
// No data today
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
|
||||
for i in 1..30 {
|
||||
// ~96 connections, 0-3 negative reports each day
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
8 * (i % 3 + 11),
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
i % 4,
|
||||
);
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
|
||||
// Data different from previous days:
|
||||
// 0 connections, 0 negative reports
|
||||
date += 1;
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::BridgeIps,
|
||||
date,
|
||||
0,
|
||||
);
|
||||
bridge_info.info_by_country.get_mut("ru").unwrap().add_info(
|
||||
BridgeInfoType::NegativeReports,
|
||||
date,
|
||||
2,
|
||||
);
|
||||
blocking_countries.insert("ru".to_string());
|
||||
|
||||
// This should be blocked because it's different in the bad direction.
|
||||
assert_eq!(
|
||||
blocked_in(&analyzer, &bridge_info, confidence, date),
|
||||
blocking_countries
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{simulation::extra_infos_server, *};
|
||||
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use tokio::{spawn, time::sleep};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_download_extra_infos() {
|
||||
let bridge_to_test =
|
||||
array_bytes::hex2array("72E12B89136B45BBC81D1EF0AC7DDDBB91B148DB").unwrap();
|
||||
|
||||
// Open test database
|
||||
let db: Db = sled::open("test_db_dei").unwrap();
|
||||
|
||||
// Delete all data in test DB
|
||||
db.clear().unwrap();
|
||||
assert!(!db.contains_key("bridges").unwrap());
|
||||
assert!(!db.contains_key(bridge_to_test).unwrap());
|
||||
|
||||
// Download and process recent extra-infos files
|
||||
update_extra_infos(
|
||||
&db,
|
||||
"https://collector.torproject.org/recent/bridge-descriptors/extra-infos/",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check that DB contains information on a bridge with high uptime
|
||||
assert!(db.contains_key("bridges").unwrap());
|
||||
let bridges: HashSet<[u8; 20]> =
|
||||
bincode::deserialize(&db.get("bridges").unwrap().unwrap()).unwrap();
|
||||
assert!(bridges.contains(&bridge_to_test));
|
||||
assert!(db.contains_key(bridge_to_test).unwrap());
|
||||
let _bridge_info: BridgeInfo =
|
||||
bincode::deserialize(&db.get(bridge_to_test).unwrap().unwrap()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_simulate_extra_infos() {
|
||||
let extra_info_str = r#"@type bridge-extra-info 1.3
|
||||
extra-info ElephantBridgeDE2 72E12B89136B45BBC81D1EF0AC7DDDBB91B148DB
|
||||
master-key-ed25519 eWxjRwAWW7n8BGG9fNa6rApmBFbe3f0xcD7dqwOICW8
|
||||
published 2024-04-06 03:51:04
|
||||
transport obfs4
|
||||
write-history 2024-04-05 04:55:22 (86400 s) 31665735680,14918491136,15423603712,36168353792,40396827648
|
||||
read-history 2024-04-05 04:55:22 (86400 s) 31799622656,15229917184,15479115776,36317251584,40444155904
|
||||
ipv6-write-history 2024-04-05 04:55:22 (86400 s) 5972127744,610078720,516897792,327949312,640708608
|
||||
ipv6-read-history 2024-04-05 04:55:22 (86400 s) 4156158976,4040448000,2935756800,4263080960,6513532928
|
||||
dirreq-write-history 2024-04-05 04:55:22 (86400 s) 625217536,646188032,618014720,584386560,600778752
|
||||
dirreq-read-history 2024-04-05 04:55:22 (86400 s) 18816000,19000320,18484224,17364992,18443264
|
||||
geoip-db-digest 44073997E1ED63E183B79DE2A1757E9997A834E3
|
||||
geoip6-db-digest C0BF46880C6C132D746683279CC90DD4B2D31786
|
||||
dirreq-stats-end 2024-04-05 06:51:23 (86400 s)
|
||||
dirreq-v3-ips ru=16,au=8,by=8,cn=8,gb=8,ir=8,mt=8,nl=8,pl=8,tn=8,tr=8,us=8
|
||||
dirreq-v3-reqs ru=72,gb=64,pl=32,cn=16,ir=16,us=16,au=8,by=8,mt=8,nl=8,tn=8,tr=8
|
||||
dirreq-v3-resp ok=216,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=328,busy=0
|
||||
dirreq-v3-direct-dl complete=0,timeout=0,running=0
|
||||
dirreq-v3-tunneled-dl complete=212,timeout=4,running=0,min=21595,d1=293347,d2=1624137,q1=1911800,d3=2066929,d4=2415000,md=2888500,d6=3264000,d7=3851333,q3=41>
|
||||
hidserv-stats-end 2024-04-05 06:51:23 (86400 s)
|
||||
hidserv-rend-relayed-cells 7924 delta_f=2048 epsilon=0.30 bin_size=1024
|
||||
hidserv-dir-onions-seen -12 delta_f=8 epsilon=0.30 bin_size=8
|
||||
hidserv-v3-stats-end 2024-04-05 12:00:00 (86400 s)
|
||||
hidserv-rend-v3-relayed-cells -4785 delta_f=2048 epsilon=0.30 bin_size=1024
|
||||
hidserv-dir-v3-onions-seen 5 delta_f=8 epsilon=0.30 bin_size=8
|
||||
padding-counts 2024-04-05 06:51:42 (86400 s) bin-size=10000 write-drop=0 write-pad=80000 write-total=79980000 read-drop=0 read-pad=1110000 read-total=7989000>
|
||||
bridge-stats-end 2024-04-05 06:51:44 (86400 s)
|
||||
bridge-ips ru=40,us=32,??=8,au=8,br=8,by=8,cn=8,de=8,eg=8,eu=8,gb=8,ge=8,hr=8,ie=8,ir=8,kp=8,lt=8,mt=8,nl=8,pl=8,ro=8,sg=8,tn=8,tr=8,vn=8
|
||||
bridge-ip-versions v4=104,v6=8
|
||||
bridge-ip-transports <OR>=56,obfs4=56
|
||||
router-digest-sha256 zK0VMl3i0B2eaeQTR03e2hZ0i8ytkuhK/psgD2J1/lQ
|
||||
router-digest F30B38390C375E1EE74BFED844177804442569E0"#;
|
||||
|
||||
let extra_info_set = ExtraInfo::parse_file(&extra_info_str);
|
||||
assert_eq!(extra_info_set.len(), 1);
|
||||
|
||||
let extra_info = extra_info_set.iter().next().unwrap().clone();
|
||||
|
||||
let extra_info_str = extra_info.to_string();
|
||||
|
||||
let extra_info_2 = ExtraInfo::parse_file(&extra_info_str)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert_eq!(extra_info, extra_info_2);
|
||||
|
||||
let bridge_to_test: [u8; 20] =
|
||||
array_bytes::hex2array("72E12B89136B45BBC81D1EF0AC7DDDBB91B148DB").unwrap();
|
||||
|
||||
// Open test database
|
||||
let db: Db = sled::open("test_db_sei").unwrap();
|
||||
|
||||
// Delete all data in test DB
|
||||
db.clear().unwrap();
|
||||
assert!(!db.contains_key("bridges").unwrap());
|
||||
assert!(!db.contains_key(bridge_to_test).unwrap());
|
||||
|
||||
// Start web server
|
||||
spawn(async move {
|
||||
extra_infos_server::server().await;
|
||||
});
|
||||
|
||||
// Give server time to start
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
|
||||
// Update extra-infos (no new data to add)
|
||||
update_extra_infos(&db, "http://localhost:8004/")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check that database is still empty
|
||||
assert!(!db.contains_key("bridges").unwrap());
|
||||
assert!(!db.contains_key(bridge_to_test).unwrap());
|
||||
|
||||
// Add our extra-info to the server's records
|
||||
{
|
||||
use hyper::{Body, Client, Method, Request};
|
||||
let client = Client::new();
|
||||
let req = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri("http://localhost:8004/add".parse::<hyper::Uri>().unwrap())
|
||||
.body(Body::from(serde_json::to_string(&extra_info_set).unwrap()))
|
||||
.unwrap();
|
||||
client.request(req).await.unwrap();
|
||||
}
|
||||
|
||||
// Update extra-infos (add new record)
|
||||
update_extra_infos(&db, "http://localhost:8004/")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check that DB now contains information on this bridge
|
||||
assert!(db.contains_key("bridges").unwrap());
|
||||
let bridges: HashSet<[u8; 20]> =
|
||||
bincode::deserialize(&db.get("bridges").unwrap().unwrap()).unwrap();
|
||||
assert!(bridges.contains(&bridge_to_test));
|
||||
assert!(db.contains_key(bridge_to_test).unwrap());
|
||||
let _bridge_info: BridgeInfo =
|
||||
bincode::deserialize(&db.get(bridge_to_test).unwrap().unwrap()).unwrap();
|
||||
}
|
|
@ -0,0 +1,577 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{bridge_verification_info::BridgeVerificationInfo, *};
|
||||
use lox_library::{
|
||||
bridge_table::{self, BridgeLine},
|
||||
cred::Lox,
|
||||
proto::*,
|
||||
scalar_u32, BridgeAuth, BridgeDb,
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use curve25519_dalek::Scalar;
|
||||
use rand::RngCore;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
struct TestHarness {
|
||||
bdb: BridgeDb,
|
||||
pub ba: BridgeAuth,
|
||||
}
|
||||
|
||||
impl TestHarness {
|
||||
fn new() -> Self {
|
||||
TestHarness::new_buckets(5, 5)
|
||||
}
|
||||
|
||||
fn new_buckets(num_buckets: u16, hot_spare: u16) -> Self {
|
||||
// Create a BridegDb
|
||||
let mut bdb = BridgeDb::new();
|
||||
// Create a BridgeAuth
|
||||
let mut ba = BridgeAuth::new(bdb.pubkey);
|
||||
|
||||
// Make 3 x num_buckets open invitation bridges, in sets of 3
|
||||
for _ in 0..num_buckets {
|
||||
let bucket = [random(), random(), random()];
|
||||
let _ = ba.add_openinv_bridges(bucket, &mut bdb);
|
||||
}
|
||||
// Add hot_spare more hot spare buckets
|
||||
for _ in 0..hot_spare {
|
||||
let bucket = [random(), random(), random()];
|
||||
let _ = ba.add_spare_bucket(bucket, &mut bdb);
|
||||
}
|
||||
// Create the encrypted bridge table
|
||||
ba.enc_bridge_table();
|
||||
|
||||
Self { bdb, ba }
|
||||
}
|
||||
|
||||
fn advance_days(&mut self, days: u16) {
|
||||
self.ba.advance_days(days);
|
||||
}
|
||||
|
||||
fn get_new_credential(&mut self) -> Lox {
|
||||
let inv = self.bdb.invite().unwrap();
|
||||
let (req, state) = open_invite::request(&inv);
|
||||
let resp = self.ba.handle_open_invite(req).unwrap();
|
||||
let (cred, _bridgeline) =
|
||||
open_invite::handle_response(state, resp, &self.ba.lox_pub).unwrap();
|
||||
cred
|
||||
}
|
||||
|
||||
fn level_up(&mut self, cred: &Lox) -> Lox {
|
||||
let current_level = scalar_u32(&cred.trust_level).unwrap();
|
||||
if current_level == 0 {
|
||||
self.advance_days(trust_promotion::UNTRUSTED_INTERVAL.try_into().unwrap());
|
||||
let (promreq, promstate) =
|
||||
trust_promotion::request(cred, &self.ba.lox_pub, self.ba.today()).unwrap();
|
||||
let promresp = self.ba.handle_trust_promotion(promreq).unwrap();
|
||||
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
|
||||
let (migreq, migstate) =
|
||||
migration::request(cred, &migcred, &self.ba.lox_pub, &self.ba.migration_pub)
|
||||
.unwrap();
|
||||
let migresp = self.ba.handle_migration(migreq).unwrap();
|
||||
let new_cred = migration::handle_response(migstate, migresp, &self.ba.lox_pub).unwrap();
|
||||
new_cred
|
||||
} else {
|
||||
self.advance_days(
|
||||
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
|
||||
let encbuckets = self.ba.enc_bridge_table();
|
||||
let bucket =
|
||||
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
|
||||
.unwrap();
|
||||
let reachcred = bucket.1.unwrap();
|
||||
let (lvreq, lvstate) = level_up::request(
|
||||
cred,
|
||||
&reachcred,
|
||||
&self.ba.lox_pub,
|
||||
&self.ba.reachability_pub,
|
||||
self.ba.today(),
|
||||
)
|
||||
.unwrap();
|
||||
let lvresp = self.ba.handle_level_up(lvreq).unwrap();
|
||||
let new_cred = level_up::handle_response(lvstate, lvresp, &self.ba.lox_pub).unwrap();
|
||||
new_cred
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bucket(&mut self, cred: &Lox) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
|
||||
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
|
||||
let encbuckets = self.ba.enc_bridge_table();
|
||||
let bucket =
|
||||
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
|
||||
.unwrap();
|
||||
bucket.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random() -> BridgeLine {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut res: BridgeLine = BridgeLine::default();
|
||||
// Pick a random 4-byte address
|
||||
let mut addr: [u8; 4] = [0; 4];
|
||||
rng.fill_bytes(&mut addr);
|
||||
// If the leading byte is 224 or more, that's not a valid IPv4
|
||||
// address. Choose an IPv6 address instead (but don't worry too
|
||||
// much about it being well formed).
|
||||
if addr[0] >= 224 {
|
||||
rng.fill_bytes(&mut res.addr);
|
||||
} else {
|
||||
// Store an IPv4 address as a v4-mapped IPv6 address
|
||||
res.addr[10] = 255;
|
||||
res.addr[11] = 255;
|
||||
res.addr[12..16].copy_from_slice(&addr);
|
||||
};
|
||||
let ports: [u16; 4] = [443, 4433, 8080, 43079];
|
||||
let portidx = (rng.next_u32() % 4) as usize;
|
||||
res.port = ports[portidx];
|
||||
res.uid_fingerprint = rng.next_u64();
|
||||
rng.fill_bytes(&mut res.fingerprint);
|
||||
let mut cert: [u8; 52] = [0; 52];
|
||||
rng.fill_bytes(&mut cert);
|
||||
let infostr: String = format!(
|
||||
"obfs4 cert={}, iat-mode=0",
|
||||
general_purpose::STANDARD_NO_PAD.encode(cert)
|
||||
);
|
||||
res.info[..infostr.len()].copy_from_slice(infostr.as_bytes());
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_reports() {
|
||||
let mut th = TestHarness::new();
|
||||
|
||||
// Get new level 1 credential
|
||||
let cred = th.get_new_credential();
|
||||
let cred = th.level_up(&cred);
|
||||
|
||||
let bridges = th.get_bucket(&cred);
|
||||
|
||||
// Create BridgeVerificationInfo for each bridge
|
||||
let mut buckets = HashSet::<Scalar>::new();
|
||||
buckets.insert(cred.bucket);
|
||||
let bridge_info_1 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[0],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
let bridge_info_2 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[1],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
let bridge_info_3 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[2],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
|
||||
// Create reports
|
||||
let report_1 =
|
||||
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox);
|
||||
let report_2 =
|
||||
NegativeReport::from_lox_bucket(bridges[1].fingerprint, cred.bucket, "ru".to_string());
|
||||
let report_3 =
|
||||
NegativeReport::from_lox_credential(bridges[2].fingerprint, &cred, "ru".to_string());
|
||||
|
||||
// Backdated reports
|
||||
let date = get_date();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let report_4 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
|
||||
&bridges[0],
|
||||
date - 1,
|
||||
nonce,
|
||||
)),
|
||||
"ru".to_string(),
|
||||
date - 1,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let report_5 = NegativeReport::new(
|
||||
bridges[1].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
|
||||
&bridges[1],
|
||||
date - 2,
|
||||
nonce,
|
||||
)),
|
||||
"ru".to_string(),
|
||||
date - 2,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let report_6 = NegativeReport::new(
|
||||
bridges[2].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
|
||||
&bridges[2],
|
||||
date - 3,
|
||||
nonce,
|
||||
)),
|
||||
"ru".to_string(),
|
||||
date - 3,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Verify reports
|
||||
assert!(report_1.verify(&bridge_info_1));
|
||||
assert!(report_2.verify(&bridge_info_2));
|
||||
assert!(report_3.verify(&bridge_info_3));
|
||||
assert!(report_4.verify(&bridge_info_1));
|
||||
assert!(report_5.verify(&bridge_info_2));
|
||||
assert!(report_6.verify(&bridge_info_3));
|
||||
|
||||
// Check that deserialization fails under invalid conditions
|
||||
|
||||
// Date in the future
|
||||
let mut invalid_report_1 =
|
||||
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox)
|
||||
.to_serializable_report();
|
||||
invalid_report_1.date = invalid_report_1.date + 2;
|
||||
|
||||
// Date too far in past
|
||||
let mut invalid_report_2 =
|
||||
NegativeReport::from_bridgeline(bridges[1], "ru".to_string(), BridgeDistributor::Lox)
|
||||
.to_serializable_report();
|
||||
invalid_report_2.date = invalid_report_2.date - MAX_BACKDATE - 1;
|
||||
|
||||
// Invalid country code
|
||||
let invalid_report_3 =
|
||||
NegativeReport::from_bridgeline(bridges[2], "xx".to_string(), BridgeDistributor::Lox)
|
||||
.to_serializable_report();
|
||||
|
||||
assert!(invalid_report_1.to_report().is_err());
|
||||
assert!(invalid_report_2.to_report().is_err());
|
||||
assert!(invalid_report_3.to_report().is_err());
|
||||
|
||||
// Check that verification fails with incorrect data
|
||||
|
||||
let date = get_date();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// Incorrect BridgeLine hash
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let invalid_report_4 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
|
||||
&BridgeLine::default(),
|
||||
date,
|
||||
nonce,
|
||||
)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Incorrect bucket hash
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let invalid_report_5 = NegativeReport::new(
|
||||
bridges[1].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO, date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
assert!(!invalid_report_4.verify(&bridge_info_1));
|
||||
assert!(!invalid_report_5.verify(&bridge_info_2));
|
||||
|
||||
// Test that reports with duplicate nonces are rejected
|
||||
// (Also test encryption and decryption.)
|
||||
|
||||
// Open test database
|
||||
let db: Db = sled::open("test_db_nr").unwrap();
|
||||
|
||||
// Delete all data in test DB
|
||||
db.clear().unwrap();
|
||||
assert!(!db.contains_key("nrs-to-process").unwrap());
|
||||
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
// A valid report
|
||||
let valid_report_1 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[0], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let valid_report_1_copy_1 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[0], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let valid_report_1_copy_2 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[0], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report which reuses this nonce
|
||||
let invalid_report_1 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[0], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// This is the same report
|
||||
assert_eq!(valid_report_1, invalid_report_1);
|
||||
|
||||
// Report which reuses this nonce for a different bridge
|
||||
let invalid_report_2 = NegativeReport::new(
|
||||
bridges[1].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[1], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report which uses this nonce but on a different day
|
||||
let valid_report_2 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
|
||||
&bridges[0],
|
||||
date - 1,
|
||||
nonce,
|
||||
)),
|
||||
"ru".to_string(),
|
||||
date - 1,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report with different nonce
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
let valid_report_3 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridges[0], date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let map_key_1 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", valid_report_1.fingerprint),
|
||||
"ru".to_string(),
|
||||
date
|
||||
);
|
||||
|
||||
// Generate key for today
|
||||
let secret = StaticSecret::random_from_rng(&mut rng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let secret_yesterday = StaticSecret::random_from_rng(&mut rng);
|
||||
let public_yesterday = PublicKey::from(&secret_yesterday);
|
||||
assert!(!db.contains_key("nr-keys").unwrap());
|
||||
|
||||
// Fail to add to database because we can't decrypt
|
||||
handle_encrypted_negative_report(&db, valid_report_1_copy_1.encrypt(&public));
|
||||
assert!(!db.contains_key("nrs-to-process").unwrap());
|
||||
|
||||
// Store yesterday's key but not today's
|
||||
let mut nr_keys = BTreeMap::<u32, StaticSecret>::new();
|
||||
nr_keys.insert(date - 1, secret_yesterday);
|
||||
db.insert("nr-keys", bincode::serialize(&nr_keys).unwrap())
|
||||
.unwrap();
|
||||
|
||||
// Fail to add to database because we still can't decrypt
|
||||
handle_encrypted_negative_report(&db, valid_report_1_copy_2.encrypt(&public));
|
||||
assert!(!db.contains_key("nrs-to-process").unwrap());
|
||||
|
||||
// Store today's key
|
||||
nr_keys.insert(date, secret);
|
||||
db.insert("nr-keys", bincode::serialize(&nr_keys).unwrap())
|
||||
.unwrap();
|
||||
|
||||
handle_encrypted_negative_report(&db, valid_report_1.encrypt(&public));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
handle_encrypted_negative_report(&db, invalid_report_1.encrypt(&public)); // no change
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
let map_key_2 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", invalid_report_2.fingerprint),
|
||||
"ru".to_string(),
|
||||
date
|
||||
);
|
||||
handle_encrypted_negative_report(&db, invalid_report_2.encrypt(&public)); // no change
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
assert!(!nrs_to_process.contains_key(&map_key_2));
|
||||
|
||||
let map_key_3 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", valid_report_2.fingerprint),
|
||||
"ru".to_string(),
|
||||
date - 1
|
||||
);
|
||||
handle_encrypted_negative_report(&db, valid_report_2.encrypt(&public_yesterday));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_3).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
handle_encrypted_negative_report(&db, valid_report_3.encrypt(&public));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 2);
|
||||
|
||||
// Same tests, but use hash of bucket
|
||||
|
||||
// Delete all data in test DB
|
||||
db.clear().unwrap();
|
||||
assert!(!db.contains_key("nrs-to-process").unwrap());
|
||||
|
||||
// Re-generate keys and save in database
|
||||
let public = new_negative_report_key(&db, date).unwrap();
|
||||
let public_yesterday = new_negative_report_key(&db, date - 1).unwrap();
|
||||
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
// A valid report
|
||||
let valid_report_1 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&cred.bucket, date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report which reuses this nonce
|
||||
let invalid_report_1 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&cred.bucket, date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// This is the same report
|
||||
assert_eq!(valid_report_1, invalid_report_1);
|
||||
|
||||
// Report which reuses this nonce for a different bridge
|
||||
let invalid_report_2 = NegativeReport::new(
|
||||
bridges[1].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&cred.bucket, date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report which uses this nonce but on a different day
|
||||
let valid_report_2 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&cred.bucket, date - 1, nonce)),
|
||||
"ru".to_string(),
|
||||
date - 1,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
// Report with different nonce
|
||||
let mut nonce = [0; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
let valid_report_3 = NegativeReport::new(
|
||||
bridges[0].fingerprint,
|
||||
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&cred.bucket, date, nonce)),
|
||||
"ru".to_string(),
|
||||
date,
|
||||
nonce,
|
||||
BridgeDistributor::Lox,
|
||||
);
|
||||
|
||||
let map_key_1 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", valid_report_1.fingerprint),
|
||||
"ru".to_string(),
|
||||
date
|
||||
);
|
||||
handle_encrypted_negative_report(&db, valid_report_1.encrypt(&public));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
handle_encrypted_negative_report(&db, invalid_report_1.encrypt(&public)); // no change
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
let map_key_2 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", invalid_report_2.fingerprint),
|
||||
"ru".to_string(),
|
||||
date
|
||||
);
|
||||
handle_encrypted_negative_report(&db, invalid_report_2.encrypt(&public)); // no change
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
assert!(!nrs_to_process.contains_key(&map_key_2));
|
||||
|
||||
let map_key_3 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", valid_report_2.fingerprint),
|
||||
"ru".to_string(),
|
||||
date - 1
|
||||
);
|
||||
handle_encrypted_negative_report(&db, valid_report_2.encrypt(&public_yesterday));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_3).unwrap();
|
||||
assert_eq!(negative_reports.len(), 1);
|
||||
|
||||
handle_encrypted_negative_report(&db, valid_report_3.encrypt(&public));
|
||||
let nrs_to_process: BTreeMap<String, Vec<SerializableNegativeReport>> =
|
||||
bincode::deserialize(&db.get("nrs-to-process").unwrap().unwrap()).unwrap();
|
||||
let negative_reports = nrs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(negative_reports.len(), 2);
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{bridge_verification_info::BridgeVerificationInfo, *};
|
||||
use lox_library::{
|
||||
bridge_table::{self, BridgeLine},
|
||||
cred::Lox,
|
||||
proto::*,
|
||||
scalar_u32, BridgeAuth, BridgeDb,
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use curve25519_dalek::{ristretto::RistrettoBasepointTable, Scalar};
|
||||
use rand::RngCore;
|
||||
use std::collections::HashSet;
|
||||
|
||||
struct TestHarness {
|
||||
bdb: BridgeDb,
|
||||
pub ba: BridgeAuth,
|
||||
}
|
||||
|
||||
impl TestHarness {
|
||||
fn new() -> Self {
|
||||
TestHarness::new_buckets(5, 5)
|
||||
}
|
||||
|
||||
fn new_buckets(num_buckets: u16, hot_spare: u16) -> Self {
|
||||
// Create a BridegDb
|
||||
let mut bdb = BridgeDb::new();
|
||||
// Create a BridgeAuth
|
||||
let mut ba = BridgeAuth::new(bdb.pubkey);
|
||||
|
||||
// Make 3 x num_buckets open invitation bridges, in sets of 3
|
||||
for _ in 0..num_buckets {
|
||||
let bucket = [random(), random(), random()];
|
||||
let _ = ba.add_openinv_bridges(bucket, &mut bdb);
|
||||
}
|
||||
// Add hot_spare more hot spare buckets
|
||||
for _ in 0..hot_spare {
|
||||
let bucket = [random(), random(), random()];
|
||||
let _ = ba.add_spare_bucket(bucket, &mut bdb);
|
||||
}
|
||||
// Create the encrypted bridge table
|
||||
ba.enc_bridge_table();
|
||||
|
||||
Self { bdb, ba }
|
||||
}
|
||||
|
||||
fn advance_days(&mut self, days: u16) {
|
||||
self.ba.advance_days(days);
|
||||
}
|
||||
|
||||
fn get_new_credential(&mut self) -> Lox {
|
||||
let inv = self.bdb.invite().unwrap();
|
||||
let (req, state) = open_invite::request(&inv);
|
||||
let resp = self.ba.handle_open_invite(req).unwrap();
|
||||
let (cred, _bridgeline) =
|
||||
open_invite::handle_response(state, resp, &self.ba.lox_pub).unwrap();
|
||||
cred
|
||||
}
|
||||
|
||||
fn level_up(&mut self, cred: &Lox) -> Lox {
|
||||
let current_level = scalar_u32(&cred.trust_level).unwrap();
|
||||
if current_level == 0 {
|
||||
self.advance_days(trust_promotion::UNTRUSTED_INTERVAL.try_into().unwrap());
|
||||
let (promreq, promstate) =
|
||||
trust_promotion::request(cred, &self.ba.lox_pub, self.ba.today()).unwrap();
|
||||
let promresp = self.ba.handle_trust_promotion(promreq).unwrap();
|
||||
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
|
||||
let (migreq, migstate) =
|
||||
migration::request(cred, &migcred, &self.ba.lox_pub, &self.ba.migration_pub)
|
||||
.unwrap();
|
||||
let migresp = self.ba.handle_migration(migreq).unwrap();
|
||||
let new_cred = migration::handle_response(migstate, migresp, &self.ba.lox_pub).unwrap();
|
||||
new_cred
|
||||
} else {
|
||||
self.advance_days(
|
||||
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
|
||||
let encbuckets = self.ba.enc_bridge_table();
|
||||
let bucket =
|
||||
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
|
||||
.unwrap();
|
||||
let reachcred = bucket.1.unwrap();
|
||||
let (lvreq, lvstate) = level_up::request(
|
||||
cred,
|
||||
&reachcred,
|
||||
&self.ba.lox_pub,
|
||||
&self.ba.reachability_pub,
|
||||
self.ba.today(),
|
||||
)
|
||||
.unwrap();
|
||||
let lvresp = self.ba.handle_level_up(lvreq).unwrap();
|
||||
let new_cred = level_up::handle_response(lvstate, lvresp, &self.ba.lox_pub).unwrap();
|
||||
new_cred
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bucket(&mut self, cred: &Lox) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
|
||||
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
|
||||
let encbuckets = self.ba.enc_bridge_table();
|
||||
let bucket =
|
||||
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
|
||||
.unwrap();
|
||||
bucket.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random() -> BridgeLine {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut res: BridgeLine = BridgeLine::default();
|
||||
// Pick a random 4-byte address
|
||||
let mut addr: [u8; 4] = [0; 4];
|
||||
rng.fill_bytes(&mut addr);
|
||||
// If the leading byte is 224 or more, that's not a valid IPv4
|
||||
// address. Choose an IPv6 address instead (but don't worry too
|
||||
// much about it being well formed).
|
||||
if addr[0] >= 224 {
|
||||
rng.fill_bytes(&mut res.addr);
|
||||
} else {
|
||||
// Store an IPv4 address as a v4-mapped IPv6 address
|
||||
res.addr[10] = 255;
|
||||
res.addr[11] = 255;
|
||||
res.addr[12..16].copy_from_slice(&addr);
|
||||
};
|
||||
let ports: [u16; 4] = [443, 4433, 8080, 43079];
|
||||
let portidx = (rng.next_u32() % 4) as usize;
|
||||
res.port = ports[portidx];
|
||||
res.uid_fingerprint = rng.next_u64();
|
||||
rng.fill_bytes(&mut res.fingerprint);
|
||||
let mut cert: [u8; 52] = [0; 52];
|
||||
rng.fill_bytes(&mut cert);
|
||||
let infostr: String = format!(
|
||||
"obfs4 cert={}, iat-mode=0",
|
||||
general_purpose::STANDARD_NO_PAD.encode(cert)
|
||||
);
|
||||
res.info[..infostr.len()].copy_from_slice(infostr.as_bytes());
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_positive_reports() {
|
||||
let mut th = TestHarness::new();
|
||||
|
||||
// Get new level 3 credential
|
||||
let cred = th.get_new_credential();
|
||||
let cred = th.level_up(&cred);
|
||||
let cred = th.level_up(&cred);
|
||||
let cred = th.level_up(&cred);
|
||||
|
||||
let bridges = th.get_bucket(&cred);
|
||||
|
||||
// Create BridgeVerificationInfo for each bridge
|
||||
let mut buckets = HashSet::<Scalar>::new();
|
||||
buckets.insert(cred.bucket);
|
||||
let bridge_info_1 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[0],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
let bridge_info_2 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[1],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
let bridge_info_3 = BridgeVerificationInfo {
|
||||
bridge_line: bridges[2],
|
||||
buckets: buckets.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
|
||||
// Create reports
|
||||
let report_1 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let report_2 = PositiveReport::from_lox_credential(
|
||||
bridges[1].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let report_3 = PositiveReport::from_lox_credential(
|
||||
bridges[2].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Compute Htable
|
||||
let H = lox_library::proto::positive_report::compute_H(report_1.date);
|
||||
let Htable = RistrettoBasepointTable::create(&H);
|
||||
|
||||
assert!(report_1.verify(&mut th.ba, &bridge_info_1, &Htable));
|
||||
assert!(report_2.verify(&mut th.ba, &bridge_info_2, &Htable));
|
||||
assert!(report_3.verify(&mut th.ba, &bridge_info_3, &Htable));
|
||||
|
||||
// Check that user cannot use credential for other bridge
|
||||
|
||||
// Get new credential
|
||||
let cred_2 = th.get_new_credential();
|
||||
let bridges_2 = th.get_bucket(&cred_2);
|
||||
|
||||
let mut buckets_2 = HashSet::<Scalar>::new();
|
||||
buckets_2.insert(cred_2.bucket);
|
||||
let bridge_info_4 = BridgeVerificationInfo {
|
||||
bridge_line: bridges_2[0],
|
||||
buckets: buckets_2.clone(),
|
||||
pubkey: None,
|
||||
};
|
||||
|
||||
// Use new credential to create positive report even we don't trust it
|
||||
let invalid_report_1 = PositiveReport::from_lox_credential(
|
||||
bridges_2[0].fingerprint,
|
||||
None,
|
||||
&cred_2,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
);
|
||||
|
||||
// Use first credential for bridge from second bucket
|
||||
let invalid_report_2 = PositiveReport::from_lox_credential(
|
||||
bridges_2[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
);
|
||||
|
||||
// Use second credential for bridge from first bucket
|
||||
let invalid_report_3 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred_2,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
);
|
||||
|
||||
// Check that all of these fail
|
||||
assert!(invalid_report_1.is_err());
|
||||
assert!(!invalid_report_2
|
||||
.unwrap()
|
||||
.verify(&mut th.ba, &bridge_info_4, &Htable));
|
||||
assert!(invalid_report_3.is_err());
|
||||
|
||||
// Check that deserialization fails under invalid conditions
|
||||
|
||||
// Date in the future
|
||||
let mut invalid_report_4 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap()
|
||||
.to_serializable_report();
|
||||
invalid_report_4.date = invalid_report_4.date + 2;
|
||||
|
||||
// Invalid country code
|
||||
let invalid_report_5 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"xx".to_string(),
|
||||
)
|
||||
.unwrap()
|
||||
.to_serializable_report();
|
||||
|
||||
assert!(invalid_report_4.to_report().is_err());
|
||||
assert!(invalid_report_5.to_report().is_err());
|
||||
|
||||
// Test storing to-be-processed positive reports to database
|
||||
|
||||
// Create reports
|
||||
let report_1 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let report_2 = PositiveReport::from_lox_credential(
|
||||
bridges[0].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let report_3 = PositiveReport::from_lox_credential(
|
||||
bridges[1].fingerprint,
|
||||
None,
|
||||
&cred,
|
||||
&th.ba.lox_pub,
|
||||
"ru".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Open test database
|
||||
let db: Db = sled::open("test_db_pr").unwrap();
|
||||
|
||||
// Delete all data in test DB
|
||||
db.clear().unwrap();
|
||||
assert!(!db.contains_key("prs-to-process").unwrap());
|
||||
|
||||
let map_key_1 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", report_1.fingerprint),
|
||||
&report_1.country,
|
||||
&report_1.date
|
||||
);
|
||||
let map_key_2 = format!(
|
||||
"{}_{}_{}",
|
||||
array_bytes::bytes2hex("", report_3.fingerprint),
|
||||
&report_3.country,
|
||||
&report_3.date
|
||||
);
|
||||
|
||||
save_positive_report_to_process(&db, report_1);
|
||||
let prs_to_process: BTreeMap<String, Vec<SerializablePositiveReport>> =
|
||||
bincode::deserialize(&db.get("prs-to-process").unwrap().unwrap()).unwrap();
|
||||
let positive_reports = prs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(positive_reports.len(), 1);
|
||||
assert!(!prs_to_process.contains_key(&map_key_2));
|
||||
|
||||
save_positive_report_to_process(&db, report_2);
|
||||
let prs_to_process: BTreeMap<String, Vec<SerializablePositiveReport>> =
|
||||
bincode::deserialize(&db.get("prs-to-process").unwrap().unwrap()).unwrap();
|
||||
let positive_reports = prs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(positive_reports.len(), 2);
|
||||
assert!(!prs_to_process.contains_key(&map_key_2));
|
||||
|
||||
save_positive_report_to_process(&db, report_3);
|
||||
let prs_to_process: BTreeMap<String, Vec<SerializablePositiveReport>> =
|
||||
bincode::deserialize(&db.get("prs-to-process").unwrap().unwrap()).unwrap();
|
||||
// Check that this has not changed
|
||||
let positive_reports = prs_to_process.get(&map_key_1).unwrap();
|
||||
assert_eq!(positive_reports.len(), 2);
|
||||
// New report added to its own collection
|
||||
let positive_reports = prs_to_process.get(&map_key_2).unwrap();
|
||||
assert_eq!(positive_reports.len(), 1);
|
||||
}
|
Loading…
Reference in New Issue