Преглед изворни кода

Implement report functions and tests for lox-distributor

Vecna пре 1 месец
родитељ
комит
db0eab8884

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
crates/lox-distributor/db_test_file.json


+ 168 - 1
crates/lox-distributor/src/lox_context.rs

@@ -1,3 +1,7 @@
+// We want Scalars to be lowercase letters, and Points and credentials
+// to be capital letters
+#![allow(non_snake_case)]
+
 use bytes::Bytes;
 #[cfg(feature = "test-branch")]
 use http_body_util::Empty;
@@ -11,8 +15,10 @@ use lox_extensions::{
     migration_table::EncMigrationTable,
     proto::{
         blockage_migration, check_blockage, errors::CredentialError, issue_invite, level_up,
-        migration, open_invite, redeem_invite, trust_promotion, update_cred, update_invite,
+        migration, open_invite, redeem_invite, report_init, report_resolve, report_status,
+        report_submit, trust_promotion, update_cred, update_invite,
     },
+    report_table::{CountryCode, ReportStatus},
     BridgeAuth, BridgeDb, OpenInvitationError,
 };
 
@@ -541,6 +547,51 @@ impl LoxServerContext {
         self.ba.lock().unwrap().handle_update_invite(old_key, req)
     }
 
+    // Returns a valid report_init::Response if the report_init::Request is valid
+    fn report_init(
+        &self,
+        req: report_init::report_init::Request,
+        cc: CountryCode,
+        D: G,
+    ) -> Result<report_init::report_init::Reply, CredentialError> {
+        let mut ba_obj = self.ba.lock().unwrap();
+        ba_obj.handle_report_init(req, cc, D)
+    }
+
+    // Returns a valid report_status::Response if the report_status::Request is valid
+    fn report_status(
+        &self,
+        req: report_status::report_status::Request,
+    ) -> Result<
+        (
+            report_status::report_status::Reply,
+            [ReportStatus; MAX_BRIDGES_PER_BUCKET],
+        ),
+        CredentialError,
+    > {
+        let mut ba_obj = self.ba.lock().unwrap();
+        ba_obj.handle_report_status(req)
+    }
+
+    // Returns a valid report_submit::Response if the report_submit::Request is valid
+    fn report_submit(
+        &self,
+        req: report_submit::report_submit::Request,
+        bridges_being_reported: [bool; MAX_BRIDGES_PER_BUCKET],
+    ) -> Result<report_submit::report_submit::Reply, CredentialError> {
+        let mut ba_obj = self.ba.lock().unwrap();
+        ba_obj.handle_report_submit(req, bridges_being_reported)
+    }
+
+    // Returns a valid report_resolve::Response if the report_resolve::Request is valid
+    fn report_resolve(
+        &self,
+        req: report_resolve::report_resolve::Request,
+    ) -> Result<report_resolve::report_resolve::Reply, CredentialError> {
+        let mut ba_obj = self.ba.lock().unwrap();
+        ba_obj.handle_report_resolve(req)
+    }
+
     // Return the serialized encrypted bridge table as an HTTP response
     pub fn send_reachability_cred(self) -> Response<BoxBody<Bytes, Infallible>> {
         let enc_table = self.encrypt_table();
@@ -858,6 +909,122 @@ impl LoxServerContext {
         }
     }
 
+    // Verify the report_init request and return the result as an HTTP response
+    pub fn verify_and_send_report_init(
+        self,
+        request: Bytes,
+    ) -> Response<BoxBody<Bytes, Infallible>> {
+        let (req, cc, D): (report_init::report_init::Request, CountryCode, G) =
+            match serde_json::from_slice(&request) {
+                Ok((req, cc, D)) => (req, cc, D),
+                Err(e) => {
+                    let response = json!({"error": e.to_string()});
+                    let val = serde_json::to_string(&response).unwrap();
+                    return prepare_header(val);
+                }
+            };
+        match self.report_init(req, cc, D) {
+            Ok(resp) => {
+                let response = serde_json::to_string(&resp).unwrap();
+                self.metrics.report_init_count.inc();
+                prepare_header(response)
+            }
+            Err(e) => {
+                println!("Invalid Report Init request, Proof Error: {:?}", e);
+                let response = json!({"error": e.to_string()});
+                let val = serde_json::to_string(&response).unwrap();
+                prepare_header(val)
+            }
+        }
+    }
+
+    // Verify the report_status request and return the result as an HTTP response
+    pub fn verify_and_send_report_status(
+        self,
+        request: Bytes,
+    ) -> Response<BoxBody<Bytes, Infallible>> {
+        let req: report_status::report_status::Request = 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);
+            }
+        };
+        match self.report_status(req) {
+            Ok(resp) => {
+                let response = serde_json::to_string(&resp).unwrap();
+                self.metrics.report_status_count.inc();
+                prepare_header(response)
+            }
+            Err(e) => {
+                println!("Invalid Report Status request, Proof Error: {:?}", e);
+                let response = json!({"error": e.to_string()});
+                let val = serde_json::to_string(&response).unwrap();
+                prepare_header(val)
+            }
+        }
+    }
+
+    // Verify the report_submit request and return the result as an HTTP response
+    pub fn verify_and_send_report_submit(
+        self,
+        request: Bytes,
+    ) -> Response<BoxBody<Bytes, Infallible>> {
+        let (req, bridges_being_reported): (
+            report_submit::report_submit::Request,
+            [bool; MAX_BRIDGES_PER_BUCKET],
+        ) = match serde_json::from_slice(&request) {
+            Ok((req, bridges_being_reported)) => (req, bridges_being_reported),
+            Err(e) => {
+                let response = json!({"error": e.to_string()});
+                let val = serde_json::to_string(&response).unwrap();
+                return prepare_header(val);
+            }
+        };
+        match self.report_submit(req, bridges_being_reported) {
+            Ok(resp) => {
+                let response = serde_json::to_string(&resp).unwrap();
+                self.metrics.report_submit_count.inc();
+                prepare_header(response)
+            }
+            Err(e) => {
+                println!("Invalid Report Submit request, Proof Error: {:?}", e);
+                let response = json!({"error": e.to_string()});
+                let val = serde_json::to_string(&response).unwrap();
+                prepare_header(val)
+            }
+        }
+    }
+
+    // Verify the report_resolve request and return the result as an HTTP response
+    pub fn verify_and_send_report_resolve(
+        self,
+        request: Bytes,
+    ) -> Response<BoxBody<Bytes, Infallible>> {
+        let req: report_resolve::report_resolve::Request = 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);
+            }
+        };
+        match self.report_resolve(req) {
+            Ok(resp) => {
+                let response = serde_json::to_string(&resp).unwrap();
+                self.metrics.report_resolve_count.inc();
+                prepare_header(response)
+            }
+            Err(e) => {
+                println!("Invalid Report Resolve request, Proof Error: {:?}", e);
+                let response = json!({"error": e.to_string()});
+                let val = serde_json::to_string(&response).unwrap();
+                prepare_header(val)
+            }
+        }
+    }
+
     #[allow(dead_code)]
     #[cfg(any(test, feature = "test-branch"))]
     fn today(&self) -> u32 {

+ 36 - 1
crates/lox-distributor/src/metrics.rs

@@ -21,6 +21,10 @@ pub struct Metrics {
     pub k_reset_count: Counter,
     pub invites_requested: Counter,
     pub invalid_endpoint_request_count: Counter,
+    pub report_init_count: Counter,
+    pub report_resolve_count: Counter,
+    pub report_status_count: Counter,
+    pub report_submit_count: Counter,
 }
 
 impl Default for Metrics {
@@ -101,7 +105,26 @@ impl Default for Metrics {
             "number of requests made to an invalid or non-existent endpoint",
         ))
         .unwrap();
-
+        let report_init_count = Counter::with_opts(Opts::new(
+            "report_init_count",
+            "number of report init requests",
+        ))
+        .unwrap();
+        let report_resolve_count = Counter::with_opts(Opts::new(
+            "report_resolve_count",
+            "number of report resolve requests",
+        ))
+        .unwrap();
+        let report_status_count = Counter::with_opts(Opts::new(
+            "report_status_count",
+            "number of report status requests",
+        ))
+        .unwrap();
+        let report_submit_count = Counter::with_opts(Opts::new(
+            "report_submit_count",
+            "number of report submit requests",
+        ))
+        .unwrap();
         Metrics {
             existing_or_updated_bridges,
             new_bridges,
@@ -120,6 +143,10 @@ impl Default for Metrics {
             k_reset_count,
             invites_requested,
             invalid_endpoint_request_count,
+            report_init_count,
+            report_resolve_count,
+            report_status_count,
+            report_submit_count,
         }
     }
 }
@@ -153,6 +180,14 @@ impl Metrics {
         r.register(Box::new(self.k_reset_count.clone())).unwrap();
         r.register(Box::new(self.invites_requested.clone()))
             .unwrap();
+        r.register(Box::new(self.report_init_count.clone()))
+            .unwrap();
+        r.register(Box::new(self.report_resolve_count.clone()))
+            .unwrap();
+        r.register(Box::new(self.report_status_count.clone()))
+            .unwrap();
+        r.register(Box::new(self.report_submit_count.clone()))
+            .unwrap();
         r
     }
 }

+ 273 - 1
crates/lox-distributor/src/request_handler.rs

@@ -1,9 +1,23 @@
+// We want Scalars to be lowercase letters, and Points and credentials
+// to be capital letters
+#![allow(non_snake_case)]
+
 use crate::lox_context::LoxServerContext;
 use bytes::Bytes;
+#[cfg(test)]
+use curve25519_dalek::scalar::Scalar;
 use http_body_util::{combinators::BoxBody, BodyExt, Full};
 use hyper::{body::Body, header::HeaderValue, Method, Request, Response, StatusCode};
+#[cfg(test)]
+use lox_extensions::{
+    bridge_table::{self, BridgeLine, MAX_BRIDGES_PER_BUCKET},
+    report_table::ReportStatus,
+};
 use std::{convert::Infallible, fmt::Debug};
 
+#[cfg(test)]
+use std::time::SystemTime;
+
 fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Infallible> {
     Full::new(chunk.into()).boxed()
 }
@@ -82,6 +96,22 @@ where
                 let bytes = req.into_body().collect().await.unwrap().to_bytes();
                 cloned_context.verify_and_send_update_invite(bytes)
             }),
+            (&Method::POST, "/reportinit") => Ok::<_, Infallible>({
+                let bytes = req.into_body().collect().await.unwrap().to_bytes();
+                cloned_context.verify_and_send_report_init(bytes)
+            }),
+            (&Method::POST, "/reportstatus") => Ok::<_, Infallible>({
+                let bytes = req.into_body().collect().await.unwrap().to_bytes();
+                cloned_context.verify_and_send_report_status(bytes)
+            }),
+            (&Method::POST, "/reportsubmit") => Ok::<_, Infallible>({
+                let bytes = req.into_body().collect().await.unwrap().to_bytes();
+                cloned_context.verify_and_send_report_submit(bytes)
+            }),
+            (&Method::POST, "/reportresolve") => Ok::<_, Infallible>({
+                let bytes = req.into_body().collect().await.unwrap().to_bytes();
+                cloned_context.verify_and_send_report_resolve(bytes)
+            }),
             _ => {
                 // Return 404 not found response.
                 cloned_context.metrics.invalid_endpoint_request_count.inc();
@@ -110,8 +140,10 @@ mod tests {
         lox_creds::{BucketReachability, Invitation, Lox, Migration},
         proto::{
             blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
-            redeem_invite, trust_promotion, update_cred, update_invite,
+            redeem_invite, report_init, report_resolve, report_status, report_submit,
+            trust_promotion, update_cred, update_invite,
         },
+        report_table::CountryCode,
     };
     use serde_json::Error;
     use std::convert::Infallible;
@@ -138,6 +170,19 @@ mod tests {
         ) -> Request<B>;
         fn updatecred(&self, request: lox_utils::UpdateCredReq) -> Request<B>;
         fn updateinvite(&self, request: lox_utils::UpdateInviteReq) -> Request<B>;
+        fn reportinit(
+            &self,
+            request: report_init::report_init::Request,
+            cc: CountryCode,
+            D: G,
+        ) -> Request<B>;
+        fn reportstatus(&self, request: report_status::report_status::Request) -> Request<B>;
+        fn reportsubmit(
+            &self,
+            request: report_submit::report_submit::Request,
+            bridges_being_reported: [bool; MAX_BRIDGES_PER_BUCKET],
+        ) -> Request<B>;
+        fn reportresolve(&self, request: report_resolve::report_resolve::Request) -> Request<B>;
     }
 
     struct LoxClientMock {}
@@ -286,6 +331,61 @@ mod tests {
                 .body(full(req_str))
                 .unwrap()
         }
+
+        fn reportinit(
+            &self,
+            request: report_init::report_init::Request,
+            cc: CountryCode,
+            D: G,
+        ) -> Request<BoxedBody> {
+            let req_str = serde_json::to_string(&(request, cc, D)).unwrap();
+            Request::builder()
+                .header("Content-Type", "application/json")
+                .method("POST")
+                .uri("http://localhost/reportinit")
+                .body(full(req_str))
+                .unwrap()
+        }
+
+        fn reportstatus(
+            &self,
+            request: report_status::report_status::Request,
+        ) -> Request<BoxedBody> {
+            let req_str = serde_json::to_string(&request).unwrap();
+            Request::builder()
+                .header("Content-Type", "application/json")
+                .method("POST")
+                .uri("http://localhost/reportstatus")
+                .body(full(req_str))
+                .unwrap()
+        }
+
+        fn reportsubmit(
+            &self,
+            request: report_submit::report_submit::Request,
+            bridges_being_reported: [bool; MAX_BRIDGES_PER_BUCKET],
+        ) -> Request<BoxedBody> {
+            let req_str = serde_json::to_string(&(request, bridges_being_reported)).unwrap();
+            Request::builder()
+                .header("Content-Type", "application/json")
+                .method("POST")
+                .uri("http://localhost/reportsubmit")
+                .body(full(req_str))
+                .unwrap()
+        }
+
+        fn reportresolve(
+            &self,
+            request: report_resolve::report_resolve::Request,
+        ) -> Request<BoxedBody> {
+            let req_str = serde_json::to_string(&request).unwrap();
+            Request::builder()
+                .header("Content-Type", "application/json")
+                .method("POST")
+                .uri("http://localhost/reportresolve")
+                .body(full(req_str))
+                .unwrap()
+        }
     }
 
     async fn body_to_string(res: Response<BoxBody<Bytes, Infallible>>) -> String {
@@ -1006,6 +1106,178 @@ mod tests {
         };
     }
 
+    async fn get_bucket(
+        context: LoxServerContext,
+        cred: Lox,
+    ) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] {
+        let (id, key) = bridge_table::from_scalar(cred.bucket.unwrap()).unwrap();
+        let encbuckets = context.ba.lock().unwrap().enc_bridge_table().clone();
+        let reach_pub = context.ba.lock().unwrap().reachability_pub.clone();
+        bridge_table::BridgeTable::decrypt_bucket(
+            id,
+            &key,
+            encbuckets.get(&id).unwrap(),
+            &reach_pub,
+        )
+        .unwrap()
+        .0
+    }
+
+    async fn get_report_init(
+        context: LoxServerContext,
+        cred: Lox,
+        cc: CountryCode,
+    ) -> Result<Lox, CMZError> {
+        let rng = &mut rand::thread_rng();
+        let lc = LoxClientMock {};
+        let ((request, state), cc, D) = report_init::request(rng, cred, cc).unwrap();
+        let report_init_request = lc.reportinit(request, cc, D);
+        let report_init_response = handle(context, report_init_request).await.unwrap();
+        assert_eq!(report_init_response.status(), StatusCode::OK);
+        let report_init_resp = body_to_string(report_init_response).await;
+        let report_init_response_obj = serde_json::from_str(&report_init_resp).unwrap();
+        report_init::handle_response(state, report_init_response_obj)
+    }
+
+    async fn get_report_status(
+        context: LoxServerContext,
+        cred: Lox,
+    ) -> Result<(Lox, [ReportStatus; MAX_BRIDGES_PER_BUCKET]), CMZError> {
+        let rng = &mut rand::thread_rng();
+        let lc = LoxClientMock {};
+        let (request, state) = report_status::request(rng, cred).unwrap();
+        let report_status_request = lc.reportstatus(request);
+        let report_status_response = handle(context, report_status_request).await.unwrap();
+        assert_eq!(report_status_response.status(), StatusCode::OK);
+        let report_status_resp = body_to_string(report_status_response).await;
+        let (report_status_response_obj, report_statuses) =
+            serde_json::from_str(&report_status_resp).unwrap();
+        report_status::handle_response(state, report_status_response_obj, report_statuses)
+    }
+
+    async fn get_report_submit(
+        context: LoxServerContext,
+        cred: Lox,
+        new_false_reports: u32,
+        bridges_being_reported: [bool; MAX_BRIDGES_PER_BUCKET],
+    ) -> Result<Lox, CMZError> {
+        let rng = &mut rand::thread_rng();
+        let lc = LoxClientMock {};
+        let ((request, state), bridges_being_reported) =
+            report_submit::request(rng, cred, new_false_reports, bridges_being_reported).unwrap();
+        let report_submit_request = lc.reportsubmit(request, bridges_being_reported);
+        let report_submit_response = handle(context, report_submit_request).await.unwrap();
+        assert_eq!(report_submit_response.status(), StatusCode::OK);
+        let report_submit_resp = body_to_string(report_submit_response).await;
+        let report_submit_response_obj = serde_json::from_str(&report_submit_resp).unwrap();
+        report_submit::handle_response(state, report_submit_response_obj)
+    }
+
+    async fn get_report_resolve(
+        context: LoxServerContext,
+        cred: Lox,
+        new_false_reports: u32,
+    ) -> Result<Lox, CMZError> {
+        let rng = &mut rand::thread_rng();
+        let lc = LoxClientMock {};
+        let (request, state) = report_resolve::request(rng, cred, new_false_reports).unwrap();
+        let report_resolve_request = lc.reportresolve(request);
+        let report_resolve_response = handle(context, report_resolve_request).await.unwrap();
+        assert_eq!(report_resolve_response.status(), StatusCode::OK);
+        let report_resolve_resp = body_to_string(report_resolve_response).await;
+        let report_resolve_response_obj = serde_json::from_str(&report_resolve_resp).unwrap();
+        report_resolve::handle_response(state, report_resolve_response_obj)
+    }
+
+    #[tokio::test]
+    async fn test_handle_report_protocols() {
+        let th = TestHarness::new();
+        // Get level 0 credential
+        let invite_response = get_invite(th.context.clone()).await.unwrap();
+        let token = get_token(invite_response).await;
+        let pubkeys_obj = get_pubkeys(th.context.clone()).await.unwrap();
+        let lox_cred = get_open_invite(th.context.clone(), token, pubkeys_obj.lox_pub.clone())
+            .await
+            .unwrap();
+
+        // Get bucket info
+        let bucket = get_bucket(th.context.clone(), lox_cred.clone()).await;
+
+        // Report init
+        let lox_cred = get_report_init(th.context.clone(), lox_cred.clone(), CountryCode::RU)
+            .await
+            .unwrap();
+        let pending1 = lox_cred.pending.unwrap();
+        assert!(pending1 != Scalar::ZERO);
+
+        // Get report status
+        let (lox_cred, report_statuses) = get_report_status(th.context.clone(), lox_cred)
+            .await
+            .unwrap();
+        assert_eq!(report_statuses, [ReportStatus::NoReport; 3]);
+
+        // Submit a report
+        let lox_cred = get_report_submit(th.context.clone(), lox_cred, 0, [true, false, false])
+            .await
+            .unwrap();
+
+        // Check status
+        let (lox_cred, report_statuses) = get_report_status(th.context.clone(), lox_cred)
+            .await
+            .unwrap();
+        assert_eq!(
+            report_statuses,
+            [
+                ReportStatus::Pending,
+                ReportStatus::NoReport,
+                ReportStatus::NoReport
+            ]
+        );
+
+        // Scan returns bridge is not blocked
+        th.context.ba.lock().unwrap().process_scan_result(
+            bucket[0].get_hashed_fingerprint(),
+            CountryCode::RU,
+            ReportStatus::NotBlocked,
+            &mut th.context.db.lock().unwrap(),
+        );
+
+        // Check status
+        let (lox_cred, report_statuses) = get_report_status(th.context.clone(), lox_cred)
+            .await
+            .unwrap();
+        assert_eq!(
+            report_statuses,
+            [
+                ReportStatus::NotBlocked,
+                ReportStatus::NoReport,
+                ReportStatus::NoReport
+            ]
+        );
+
+        // Submit a new report, adding 1 to our false_reports count
+        let lox_cred = get_report_submit(th.context.clone(), lox_cred, 1, [true, false, false])
+            .await
+            .unwrap();
+        assert_eq!(lox_cred.false_reports.unwrap(), Scalar::ONE);
+
+        // Let report time out
+        th.context
+            .ba
+            .lock()
+            .unwrap()
+            .mark_old_reports_simulated_time(
+                &bucket[0].get_hashed_fingerprint(),
+                CountryCode::RU,
+                SystemTime::now(),
+            );
+
+        // Resolve
+        let _lox_cred = get_report_resolve(th.context.clone(), lox_cred, 0)
+            .await
+            .unwrap();
+    }
+
     fn empty() -> BoxBody<Bytes, Infallible> {
         Empty::<Bytes>::new()
             .map_err(|never| match never {})

+ 49 - 2
crates/lox-extensions/src/lib.rs

@@ -58,14 +58,14 @@ pub mod report_table;
 
 #[cfg(feature = "bridgeauth")]
 use bridge_table::{
-    BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY,
+    from_scalar, BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY,
 };
 #[cfg(feature = "bridgeauth")]
 use lox_creds::*;
 #[cfg(feature = "bridgeauth")]
 use migration_table::{MigrationTable, MigrationType};
 #[cfg(feature = "bridgeauth")]
-use report_table::ReportTable;
+use report_table::{CountryCode, ReportStatus, ReportTable};
 #[cfg(feature = "bridgeauth")]
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "bridgeauth")]
@@ -73,6 +73,9 @@ use std::collections::HashSet;
 #[cfg(any(feature = "bridgeauth", test))]
 use thiserror::Error;
 
+#[cfg(any(feature = "test", all(test, feature = "bridgeauth")))]
+use std::time::SystemTime;
+
 // Users can multiply their bucket values times H to identify their
 // bucket to the server without revealing it in the clear. We do not
 // need to change H periodically because we are not using it to restrict
@@ -1015,12 +1018,56 @@ impl BridgeAuth {
         &self.bridge_table.encbuckets
     }
 
+    /// Process the report of a scan to determine whether a bridge was
+    /// blocked or not
+    pub fn process_scan_result(&mut self, bridge_hashed_fingerprint: [u8; 20], cc: CountryCode, status: ReportStatus, bdb: &mut BridgeDb) {
+        if let Some(bucket) = self.report_table.process_scan_result_and_return_bucket(&bridge_hashed_fingerprint, cc, status) {
+            if status == ReportStatus::Blocked {
+                // Mark the bridge as blocked
+
+                // TODO: This should be made more robust by having a map
+                // of bridge hashed fingerprints to the buckets
+                // containing them or something like that.
+
+                let (id, _key) = match from_scalar(bucket) {
+                    Ok((id, key)) => (id, key),
+                    Err(_) => {return;},
+                };
+
+                let mut bridge_to_block = None;
+
+                if let Some(bridges) = self.bridge_table.buckets.get(&id) {
+                    for bl in bridges {
+                        if bl.get_hashed_fingerprint() == bridge_hashed_fingerprint {
+                            bridge_to_block = Some(bl.clone());
+                        }
+                    }
+                }
+
+                if let Some(bl) = bridge_to_block {
+                    self.bridge_blocked(&bl, bdb);
+                }
+            }
+        }
+    }
+
     // For testing only: manually advance the day by the given number
     // of days
     #[cfg(feature = "test")]
     pub fn advance_days(&mut self, days: u16) {
         self.time_offset += time::Duration::days(days.into());
     }
+
+    // For testing: time out old reports
+    #[cfg(any(test, feature = "test"))]
+    pub fn mark_old_reports_simulated_time(
+        &mut self,
+        bridge_hashed_fingerprint: &[u8; 20],
+        cc: CountryCode,
+        current_time: SystemTime,
+    ) {
+        self.report_table.mark_old_reports_simulated_time(bridge_hashed_fingerprint, cc, current_time);
+    }
 }
 
 pub fn scalar_u32(s: &Scalar) -> Option<u32> {

+ 29 - 1
crates/lox-extensions/src/report_table.rs

@@ -525,10 +525,38 @@ impl ReportTable {
         }
     }
 
+    // Presumably if we received a scan result, that means we have a
+    // report from a user about this. We're going to get the scalar
+    // representing the bucket based on the pending_users records.
+    pub fn process_scan_result_and_return_bucket(
+        &mut self,
+        bridge: &[u8; 20],
+        cc: CountryCode,
+        status: ReportStatus,
+    ) -> Option<Scalar> {
+        // TODO: This should be made more robust by having a map of
+        // bridge hashed fingerprints to the buckets containing them or
+        // something like that.
+
+        let mut bucket = None;
+
+        if let Some(bridge_data) = self.data.get_mut(&(*bridge, cc)) {
+            for (_time, pending) in &bridge_data.new_reports {
+                if self.pending_users.contains_key(&pending) {
+                    bucket = Some(self.pending_users.get(&pending).unwrap().0);
+                    break;
+                }
+            }
+            bridge_data.process_scan_result(status)
+        }
+
+        bucket
+    }
+
     // Essentially mark_old_reports from BridgeReportInfo, except "old"
     // means older than the timestamp passed as a parameter. This is for
     // testing, when we need to simulate the passage of time.
-    #[cfg(test)]
+    #[cfg(any(test, feature = "test"))]
     pub fn mark_old_reports_simulated_time(
         &mut self,
         bridge: &[u8; 20],

+ 2 - 0
crates/lox-utils/src/lib.rs

@@ -285,6 +285,8 @@ pub fn random() -> BridgeLine {
     let portidx = (rng.next_u32() % 4) as usize;
     res.port = ports[portidx];
     res.uid_fingerprint = rng.next_u64();
+    // We need different bridges to have different unhashed fingerprints
+    rng.fill_bytes(&mut res.unhashed_fingerprint);
     let mut cert: [u8; 52] = [0; 52];
     rng.fill_bytes(&mut cert);
     let infostr: String = format!(

Неке датотеке нису приказане због велике количине промена