Skip to content
Snippets Groups Projects
Commit 6a0ff0d8 authored by Vecna's avatar Vecna
Browse files

Begin implementing Troll Patrol handler

parent 503c0269
No related branches found
No related tags found
No related merge requests found
Pipeline #151784 failed
......@@ -36,6 +36,10 @@ prometheus = "0.13.3"
sled = "0.34.7"
prometheus-client = "0.22.0"
thiserror = "1"
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
troll-patrol = { git = "https://git-crysp.uwaterloo.ca/vvecna/troll-patrol", version = "0.1.0" }
array-bytes = "6.2.0"
sha1 = "0.10"
[dependencies.chrono]
version = "0.4.31"
......
......@@ -4,6 +4,7 @@
},
"lox_authority_port": 8001,
"troll_patrol_port": 8002,
"metrics_port": 5222,
"bridge_config": {
"watched_blockages": [
......
......@@ -5,7 +5,9 @@ use crate::{lox_context, DbConfig};
use chrono::{naive::Days, DateTime, Local, NaiveDateTime, Utc};
use lox_library::{BridgeAuth, BridgeDb};
use sled::IVec;
use std::collections::HashMap;
use thiserror::Error;
use troll_patrol::bridge_info::BridgeInfo as TPBridgeInfo;
#[derive(Error, Debug)]
pub enum LoxDBError {
......@@ -78,6 +80,9 @@ impl DB {
ba: Arc::new(Mutex::new(new_ba)),
extra_bridges: Arc::new(Mutex::new(Vec::new())),
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(
HashMap::<[u8; 20], TPBridgeInfo>::new(),
)),
metrics,
};
}
......@@ -169,6 +174,7 @@ mod tests {
#[test]
fn test_write_context() {
// TODO: Fix db_test_file.json
env::set_var("TEST_FILE_PATH", "db_test_file.json");
let (mut lox_db, _context) =
DB::open_new_or_existing_db(DbConfig::default(), None, Metrics::default()).unwrap();
......
// Allow points to be capital letters
#![allow(non_snake_case)]
use curve25519_dalek::{ristretto::RistrettoBasepointTable, Scalar};
use hyper::{body::Bytes, header::HeaderValue, Body, Response};
use lox_library::{
bridge_table::{BridgeLine, EncryptedBucket, MAX_BRIDGES_PER_BUCKET},
proto::{
blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
redeem_invite, trust_promotion,
positive_report, redeem_invite, trust_promotion,
},
BridgeAuth, BridgeDb, IssuerPubKey, OpenInvitationError,
};
......@@ -14,10 +18,15 @@ use lox_zkp::ProofError;
use serde_json::json;
use std::{
cmp::Ordering,
collections::HashMap,
collections::{BTreeMap, HashMap},
ops::DerefMut,
sync::{Arc, Mutex},
};
use troll_patrol::{
self, bridge_info::BridgeInfo as TPBridgeInfo, negative_report::*, positive_report::*,
};
use crate::metrics::Metrics;
use crate::resource_parser::{parse_into_bridgelines, sort_for_parsing};
......@@ -27,6 +36,8 @@ pub struct LoxServerContext {
pub ba: Arc<Mutex<BridgeAuth>>,
pub extra_bridges: Arc<Mutex<Vec<BridgeLine>>>,
pub to_be_replaced_bridges: Arc<Mutex<Vec<BridgeLine>>>,
// Map of bridge fingerprint to values needed to verify TP reports
pub tp_bridge_infos: Arc<Mutex<HashMap<[u8; 20], TPBridgeInfo>>>,
#[serde(skip)]
pub metrics: Metrics,
}
......@@ -735,6 +746,113 @@ impl LoxServerContext {
self.advance_days_test(req);
self.send_today()
}
// Troll Patrol-related tasks
// Verify one negative report, return true if verification succeeds
pub fn verify_negative_report(&self, report: NegativeReport) -> bool {
match self
.tp_bridge_infos
.lock()
.unwrap()
.get(&report.fingerprint)
{
Some(bridge_info) => report.verify(&bridge_info),
None => false,
}
}
// Verify multiple negative reports, return count of verified reports
// Note: This should be called on reports with the same (bridge, country, date)
pub fn verify_negative_reports(self, request: Bytes) -> Response<Body> {
let mut reports: BTreeMap<String, u32> = match serde_json::from_slice(&request) {
Ok(req) => req,
Err(e) => {
let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
return prepare_header(val);
}
};
let mut count: u32 = 0;
while let Some((serializable_report, num_reports)) = reports.pop_first() {
let val = {
match NegativeReport::from_json(serializable_report) {
Ok(report) => {
let fingerprint = array_bytes::bytes2hex("", &report.fingerprint);
if self.verify_negative_report(report) {
println!(
"Report with fingerprint {} passed verification",
fingerprint
);
1
} else {
println!(
"Report with fingerprint {} failed verification",
fingerprint
);
0
}
}
Err(_) => 0,
}
};
count += val * num_reports;
}
prepare_header(count.to_string())
}
// Verify one positive report, return true if verification succeeds
pub fn verify_positive_report(
&self,
report: PositiveReport,
Htable: &RistrettoBasepointTable,
) -> bool {
let mut binding = self.ba.lock().unwrap();
let la = binding.deref_mut();
match self
.tp_bridge_infos
.lock()
.unwrap()
.get(&report.fingerprint)
{
Some(bridge_info) => report.verify(la, &bridge_info, &Htable),
None => false,
}
}
pub fn verify_positive_reports(
self,
request: Bytes,
Htables: &mut HashMap<u32, RistrettoBasepointTable>,
) -> Response<Body> {
let mut reports: Vec<SerializablePositiveReport> = match serde_json::from_slice(&request) {
Ok(req) => req,
Err(e) => {
let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
return prepare_header(val);
}
};
let mut count: u32 = 0;
while let Some(serializable_report) = reports.pop() {
let report = serializable_report.to_report();
if report.is_ok() {
let report = report.unwrap();
let Htable = if Htables.contains_key(&report.date) {
Htables.get(&report.date).unwrap().clone()
} else {
let H = positive_report::compute_H(report.date);
let Htable: RistrettoBasepointTable = RistrettoBasepointTable::create(&H);
Htables.insert(report.date, Htable.clone());
Htable
};
if self.verify_positive_report(report, &Htable) {
count += 1;
}
}
}
prepare_header(count.to_string())
}
}
// Prepare HTTP Response for successful Server Request
......
// Allow points to be capital letters
#![allow(non_snake_case)]
use clap::Parser;
use curve25519_dalek::ristretto::RistrettoBasepointTable;
use futures::future;
use hyper::{
server::conn::AddrStream,
......@@ -11,6 +15,7 @@ use rdsys_backend::{proto::ResourceState, request_resources};
use serde::Deserialize;
use std::{
collections::HashMap,
convert::Infallible,
fs::File,
io::BufReader,
......@@ -28,6 +33,8 @@ mod request_handler;
use request_handler::handle;
mod resource_parser;
use resource_parser::{parse_into_bridgelines, parse_into_buckets};
mod troll_patrol_handler;
use troll_patrol_handler::handle as tp_handle;
use tokio::{
signal, spawn,
......@@ -64,6 +71,7 @@ struct Config {
db: DbConfig,
metrics_port: u16,
lox_authority_port: u16,
troll_patrol_port: u16,
bridge_config: BridgeConfig,
rtype: ResourceInfo,
}
......@@ -182,12 +190,13 @@ async fn create_context_manager(
db_config: DbConfig,
bridge_config: BridgeConfig,
roll_back_date: Option<String>,
Htables: HashMap<u32, RistrettoBasepointTable>,
metrics: Metrics,
context_rx: mpsc::Receiver<Command>,
mut kill: broadcast::Receiver<()>,
) {
tokio::select! {
create_context = context_manager(db_config, bridge_config, roll_back_date, metrics, context_rx) => create_context,
create_context = context_manager(db_config, bridge_config, roll_back_date, Htables, metrics, context_rx) => create_context,
_ = kill.recv() => {println!("Shut down context_manager");},
}
}
......@@ -199,6 +208,7 @@ async fn context_manager(
db_config: DbConfig,
bridge_config: BridgeConfig,
roll_back_date: Option<String>,
mut Htables: HashMap<u32, RistrettoBasepointTable>,
metrics: Metrics,
mut context_rx: mpsc::Receiver<Command>,
) {
......@@ -253,6 +263,14 @@ async fn context_manager(
lox_db.write_context(context.clone());
sleep(Duration::from_millis(1)).await;
}
TpRequest { req, sender } => {
let response = tp_handle(context.clone(), &mut Htables, req).await;
if let Err(e) = sender.send(response) {
eprintln!("Server Response Error: {:?}", e);
};
lox_db.write_context(context.clone());
sleep(Duration::from_millis(1)).await;
}
Shutdown { shutdown_sig } => {
lox_db.write_context(context.clone());
println!("Sending Shutdown Signal, all threads should shutdown.");
......@@ -273,6 +291,10 @@ enum Command {
req: Request<Body>,
sender: oneshot::Sender<Result<Response<Body>, Infallible>>,
},
TpRequest {
req: Request<Body>,
sender: oneshot::Sender<Result<Response<Body>, Infallible>>,
},
Shutdown {
shutdown_sig: broadcast::Sender<()>,
},
......@@ -289,6 +311,7 @@ async fn main() {
let (rdsys_tx, context_rx) = mpsc::channel(32);
let request_tx = rdsys_tx.clone();
let tp_request_tx = rdsys_tx.clone();
let shutdown_cmd_tx = rdsys_tx.clone();
// create the shutdown broadcast channel and clone for every thread
......@@ -320,11 +343,17 @@ async fn main() {
let metrics_handler =
spawn(async move { start_metrics_collector(metrics_addr, registry, kill_metrics).await });
// Store a basepoint table for each day's H for TP positive reports.
// RistrettoBasepointTables are not serializable, so don't save these to disk.
// Just keep a map during each run and recompute when we restart.
let Htables = HashMap::<u32, RistrettoBasepointTable>::new();
let context_manager = spawn(async move {
create_context_manager(
config.db,
config.bridge_config,
args.roll_back_date,
Htables,
metrics,
context_rx,
kill_context,
......@@ -356,12 +385,39 @@ async fn main() {
async move { Ok::<_, Infallible>(service) }
});
let addr = SocketAddr::from(([127, 0, 0, 1], config.lox_authority_port));
let server = Server::bind(&addr).serve(make_service);
let tp_make_service = make_service_fn(move |_conn: &AddrStream| {
let request_tx = tp_request_tx.clone();
let service = service_fn(move |req| {
let request_tx = request_tx.clone();
let (response_tx, response_rx) = oneshot::channel();
let cmd = Command::TpRequest {
req,
sender: response_tx,
};
async move {
request_tx.send(cmd).await.unwrap();
response_rx.await.unwrap()
}
});
async move { Ok::<_, Infallible>(service) }
});
// Public address
let pub_addr = SocketAddr::from(([127, 0, 0, 1], config.lox_authority_port));
let server = Server::bind(&pub_addr).serve(make_service);
let graceful = server.with_graceful_shutdown(shutdown_signal());
println!("Listening on {}", addr);
if let Err(e) = graceful.await {
eprintln!("server error: {}", e);
// Address for connections from Troll Patrol
let tp_addr = SocketAddr::from(([127, 0, 0, 1], config.troll_patrol_port));
let tp_server = Server::bind(&tp_addr).serve(tp_make_service);
let tp_graceful = tp_server.with_graceful_shutdown(shutdown_signal());
println!("Listening on {}", pub_addr);
println!("Listening on {}", tp_addr);
let (a, b) = future::join(graceful, tp_graceful).await;
if a.is_err() {
eprintln!("server error: {}", a.unwrap_err());
}
if b.is_err() {
eprintln!("server error: {}", b.unwrap_err());
}
future::join_all([
metrics_handler,
......
......@@ -247,6 +247,10 @@ mod tests {
ba: Arc::new(Mutex::new(lox_auth)),
extra_bridges: Arc::new(Mutex::new(Vec::new())),
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(std::collections::HashMap::<
[u8; 20],
troll_patrol::bridge_info::BridgeInfo,
>::new())),
metrics: Metrics::default(),
};
Self { context }
......@@ -307,6 +311,7 @@ mod tests {
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!(
......
......@@ -19,11 +19,9 @@ pub fn parse_into_bridgelines(
let resource_uid = resource
.get_uid()
.expect("Unable to get Fingerprint UID of resource");
let fingerprint: [u8; 20] = array_bytes::hex2array(&resource.fingerprint).expect("Failed to parse bridge fingerprint");
let infostr: String = format!(
"type={} params={:?}",
resource.r#type, resource.params,
);
let fingerprint: [u8; 20] = array_bytes::hex2array(&resource.fingerprint)
.expect("Failed to parse bridge fingerprint");
let infostr: String = format!("type={} params={:?}", resource.r#type, resource.params,);
let mut info_bytes: [u8; BRIDGE_INFO_BYTES] = [0; BRIDGE_INFO_BYTES];
info_bytes[..infostr.len()].copy_from_slice(infostr.as_bytes());
......
use crate::lox_context::LoxServerContext;
use curve25519_dalek::ristretto::RistrettoBasepointTable;
use hyper::{body, header::HeaderValue, Body, Method, Request, Response, StatusCode};
use std::{collections::HashMap, convert::Infallible};
// Handle for each Troll Patrol request/protocol
pub async fn handle(
cloned_context: LoxServerContext,
Htables: &mut HashMap<u32, RistrettoBasepointTable>,
req: Request<Body>,
) -> Result<Response<Body>, Infallible> {
match req.method() {
&Method::OPTIONS => Ok(Response::builder()
.header("Access-Control-Allow-Origin", HeaderValue::from_static("*"))
.header("Access-Control-Allow-Headers", "accept, content-type")
.header("Access-Control-Allow-Methods", "POST")
.status(200)
.body(Body::from("Allow POST"))
.unwrap()),
_ => match (req.method(), req.uri().path()) {
(&Method::POST, "/verifynegative") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.verify_negative_reports(bytes)
}),
(&Method::POST, "/verifypositive") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.verify_positive_reports(bytes, Htables)
}),
_ => {
// Return 404 not found response.
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("Not found"))
.unwrap())
}
},
}
}
#[cfg(test)]
mod tests {
use crate::lox_context;
use crate::metrics::Metrics;
use base64::{engine::general_purpose, Engine as _};
use curve25519_dalek::Scalar;
use lox_library::{
bridge_table::{self, BridgeLine, BridgeTable},
cred::Lox,
proto::*,
scalar_u32, BridgeAuth, BridgeDb,
};
use rand::RngCore;
use sha1::{Digest, Sha1};
use std::{
collections::{BTreeMap, HashSet},
sync::{Arc, Mutex},
};
use troll_patrol::{
bridge_info::BridgeInfo,
negative_report::{NegativeReport, SerializableNegativeReport},
positive_report::{PositiveReport, SerializablePositiveReport},
BridgeDistributor,
};
use super::*;
trait TpClient {
fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body>;
fn verifypositive(&self, reports: Vec<SerializablePositiveReport>) -> Request<Body>;
}
struct TpClientMock {}
impl TpClient for TpClientMock {
fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body> {
let req = serde_json::to_string(&reports).unwrap();
Request::builder()
.method("POST")
.uri("http://localhost/verifynegative")
.body(Body::from(req))
.unwrap()
}
fn verifypositive(&self, reports: Vec<SerializablePositiveReport>) -> Request<Body> {
let req = serde_json::to_string(&reports).unwrap();
Request::builder()
.method("POST")
.uri("http://localhost/verifypositive")
.body(Body::from(req))
.unwrap()
}
}
struct TestHarness {
context: LoxServerContext,
}
impl TestHarness {
fn new() -> Self {
let mut bridgedb = BridgeDb::new();
let mut lox_auth = BridgeAuth::new(bridgedb.pubkey);
// Make 3 x num_buckets open invitation bridges, in sets of 3
for _ in 0..5 {
let bucket = [random(), random(), random()];
let _ = lox_auth.add_openinv_bridges(bucket, &mut bridgedb);
}
// Add hot_spare more hot spare buckets
for _ in 0..5 {
let bucket = [random(), random(), random()];
let _ = lox_auth.add_spare_bucket(bucket, &mut bridgedb);
}
// Create the encrypted bridge table
lox_auth.enc_bridge_table();
let context = lox_context::LoxServerContext {
db: Arc::new(Mutex::new(bridgedb)),
ba: Arc::new(Mutex::new(lox_auth)),
extra_bridges: Arc::new(Mutex::new(Vec::new())),
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
tp_bridge_infos: Arc::new(Mutex::new(HashMap::<[u8; 20], BridgeInfo>::new())),
metrics: Metrics::default(),
};
Self { context }
}
pub fn generate_bridge_infos(&self) {
// We want to ignore empty bridgelines
let mut hasher = Sha1::new();
hasher.update([0; 20]);
let empty_bridgeline_fingerprint: [u8; 20] = hasher.finalize().into();
let mut lox_auth = self.context.ba.lock().unwrap();
// Recompute table
let mut tp_bridge_infos = self.context.tp_bridge_infos.lock().unwrap();
tp_bridge_infos.clear();
// Go through all buckets and all bridges in buckets, map bridge to
// buckets containing it. Note that a bridge may be contained within
// multiple buckets (open invitaion buckets and invite-only buckets).
let buckets = &lox_auth.bridge_table.buckets;
for id in buckets.keys() {
let bridges = buckets.get(id).unwrap();
let key = lox_auth.bridge_table.keys.get(id).unwrap();
let bucket = bridge_table::to_scalar(*id, key);
for bridge in bridges {
// Get hashed fingerprint
let mut hasher = Sha1::new();
hasher.update(&bridge.fingerprint);
let fingerprint: [u8; 20] = hasher.finalize().into();
if fingerprint != empty_bridgeline_fingerprint {
// Add new entry or add bucket to existing entry
if tp_bridge_infos.contains_key(&fingerprint) {
tp_bridge_infos
.get_mut(&fingerprint)
.unwrap()
.buckets
.insert(bucket);
} else {
let mut buckets = HashSet::<Scalar>::new();
buckets.insert(bucket);
tp_bridge_infos.insert(
fingerprint,
BridgeInfo {
bridge_line: *bridge,
buckets: buckets,
pubkey: None, // TODO: add pubkey for signed bridge tokens
},
);
}
}
}
}
}
}
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
}
async fn body_to_string(res: Response<Body>) -> String {
let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();
String::from_utf8(body_bytes.to_vec()).unwrap()
}
async fn get_new_credential(th: &mut TestHarness) -> Lox {
let inv = th.context.db.lock().unwrap().invite().unwrap();
let (req, state) = open_invite::request(&inv);
let resp = th
.context
.ba
.lock()
.unwrap()
.handle_open_invite(req)
.unwrap();
let (cred, _bridgeline) =
open_invite::handle_response(state, resp, &th.context.ba.lock().unwrap().lox_pub)
.unwrap();
cred
}
async fn level_up(th: &mut TestHarness, cred: &Lox) -> Lox {
let current_level = scalar_u32(&cred.trust_level).unwrap();
if current_level == 0 {
th.context
.advance_days_test(trust_promotion::UNTRUSTED_INTERVAL.try_into().unwrap());
let mut ba = th.context.ba.lock().unwrap();
let (promreq, promstate) =
trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap();
let promresp = ba.handle_trust_promotion(promreq).unwrap();
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
let (migreq, migstate) =
migration::request(cred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap();
let migresp = ba.handle_migration(migreq).unwrap();
let new_cred = migration::handle_response(migstate, migresp, &ba.lox_pub).unwrap();
new_cred
} else {
th.context.advance_days_test(
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
.try_into()
.unwrap(),
);
let mut ba = th.context.ba.lock().unwrap();
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let encbuckets = 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,
&ba.lox_pub,
&ba.reachability_pub,
ba.today(),
)
.unwrap();
let lvresp = ba.handle_level_up(lvreq).unwrap();
let new_cred = level_up::handle_response(lvstate, lvresp, &ba.lox_pub).unwrap();
new_cred
}
}
#[tokio::test]
async fn test_negative_reports() {
let mut th = TestHarness::new();
th.generate_bridge_infos();
let tpc = TpClientMock {};
let mut Htables = HashMap::<u32, RistrettoBasepointTable>::new();
// Get new level 1 credential
let cred = get_new_credential(&mut th).await;
let cred = level_up(&mut th, &cred).await;
th.generate_bridge_infos();
let mut ba = th.context.ba.lock().unwrap();
// Get bucket
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
let bridges = bucket.0;
// Create random number of negative reports for each bridge in bucket
let mut rng = rand::thread_rng();
let num_report_1 = rng.next_u32() % 4 + 1;
let num_report_2 = rng.next_u32() % 4 + 1;
let num_report_3 = rng.next_u32() % 4 + 1;
let mut reports = BTreeMap::<String, u32>::new();
let report_1 =
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox);
println!(
"report_1: {}, count: {}",
array_bytes::bytes2hex("", report_1.fingerprint),
num_report_1
);
reports.insert(report_1.to_json(), num_report_1);
let report_2 =
NegativeReport::from_lox_bucket(bridges[1].fingerprint, cred.bucket, "ru".to_string());
println!(
"report_2: {}, count: {}",
array_bytes::bytes2hex("", report_2.fingerprint),
num_report_2
);
reports.insert(report_2.to_json(), num_report_2);
let report_3 =
NegativeReport::from_lox_credential(bridges[2].fingerprint, cred, "ru".to_string());
println!(
"report_3: {}, count: {}",
array_bytes::bytes2hex("", report_3.fingerprint),
num_report_3
);
reports.insert(report_3.to_json(), num_report_3);
// TODO: Check reports with invalid fields
// TODO: Check well-formed reports with incorrect bridge data
let request = tpc.verifynegative(reports);
let response = handle(th.context.clone(), &mut Htables, request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let count: u32 = body_to_string(response).await.parse().unwrap();
assert_eq!(num_report_1 + num_report_2 + num_report_3, count);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment