use crate::lox_context; use crate::lox_context::LoxServerContext; use hyper::{body, header::HeaderValue, Body, Method, Request, Response, StatusCode}; use std::convert::Infallible; // Lox Request handling logic for each Lox request/protocol pub async fn handle( cloned_context: LoxServerContext, req: Request, ) -> Result, Infallible> { println!("Request: {:?}", req); 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, "/invite") => { Ok::<_, Infallible>(lox_context::generate_invite(cloned_context)) } (&Method::POST, "/reachability") => { Ok::<_, Infallible>(lox_context::send_reachability_cred(cloned_context)) } (&Method::POST, "/pubkeys") => { Ok::<_, Infallible>(lox_context::send_keys(cloned_context)) } (&Method::POST, "/openreq") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_open_cred(bytes, cloned_context) }), (&Method::POST, "/trustpromo") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_trust_promo(bytes, cloned_context) }), (&Method::POST, "/trustmig") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_trust_migration(bytes, cloned_context) }), (&Method::POST, "/levelup") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_level_up(bytes, cloned_context) }), (&Method::POST, "/issueinvite") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_issue_invite(bytes, cloned_context) }), (&Method::POST, "/redeem") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_redeem_invite(bytes, cloned_context) }), (&Method::POST, "/checkblockage") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); // TEST ONLY: Block all existing bridges and add new ones for migration lox_context::verify_and_send_check_blockage(bytes, cloned_context) }), (&Method::POST, "/blockagemigration") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_blockage_migration(bytes, cloned_context) }), _ => { // Return 404 not found response. Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("Not found")) .unwrap()) } }, } } #[cfg(test)] mod tests { use super::*; use chrono::{Duration, Utc}; use julianday::JulianDay; use lox::{cred::BucketReachability, proto, BridgeAuth, BridgeDb}; use lox_utils; use std::sync::{Arc, Mutex}; trait LoxClient { fn invite(&self) -> Request; fn reachability(&self) -> Request; fn pubkeys(&self) -> Request; fn openinvite(&self, request: proto::open_invite::Request) -> Request; fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request; fn trustmigration(&self, request: proto::migration::Request) -> Request; fn levelup(&self, request: proto::level_up::Request) -> Request; fn issueinvite(&self, request: proto::issue_invite::Request) -> Request; fn redeeminvite(&self, request: proto::redeem_invite::Request) -> Request; } struct LoxClientMock {} impl LoxClient for LoxClientMock { fn invite(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/invite") .body(Body::empty()) .unwrap(); req } fn reachability(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/reachability") .body(Body::empty()) .unwrap(); req } fn pubkeys(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/pubkeys") .body(Body::empty()) .unwrap(); req } fn openinvite(&self, request: proto::open_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/openreq") .body(Body::from(req_str)) .unwrap(); req } fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/trustpromo") .body(Body::from(req_str)) .unwrap(); req } fn trustmigration(&self, request: proto::migration::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/trustmig") .body(Body::from(req_str)) .unwrap(); req } fn levelup(&self, request: proto::level_up::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/levelup") .body(Body::from(req_str)) .unwrap(); req } fn issueinvite(&self, request: proto::issue_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/issueinvite") .body(Body::from(req_str)) .unwrap(); req } fn redeeminvite(&self, request: proto::redeem_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/redeem") .body(Body::from(req_str)) .unwrap(); req } } 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 = [ lox_context::random(), lox_context::random(), lox_context::random(), ]; lox_auth.add_openinv_bridges(bucket, &mut bridgedb); } // Add hot_spare more hot spare buckets for _ in 0..5 { let bucket = [ lox_context::random(), lox_context::random(), lox_context::random(), ]; lox_auth.add_spare_bucket(bucket); } // 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())), unreplaced_bridges: Arc::new(Mutex::new(Vec::new())), }; Self { context } } fn advance_days(&mut self, days: u16) { self.context.advance_days_TEST(days) } } // This should only be used for testing, use today in production fn test_today(days: i64) -> u32 { let naive_now_plus = (Utc::now() + Duration::days(days)).date_naive(); JulianDay::from(naive_now_plus).inner().try_into().unwrap() } async fn body_to_string(res: Response) -> String { let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap(); String::from_utf8(body_bytes.to_vec()).unwrap() } #[tokio::test] async fn test_handle() { let mut th = TestHarness::new(); let lc = LoxClientMock {}; // Test Random page let four_oh_four_req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/givemecreds") .body(Body::empty()) .unwrap(); let not_found_response = handle(th.context.clone(), four_oh_four_req).await.unwrap(); assert_eq!(not_found_response.status(), StatusCode::NOT_FOUND); // Test Invite let invite_request = lc.invite(); let invite_response = handle(th.context.clone(), invite_request).await.unwrap(); assert_eq!(invite_response.status(), StatusCode::OK); // Test Reachability let reachability_request = lc.reachability(); let reachability_response = handle(th.context.clone(), reachability_request) .await .unwrap(); assert_eq!(reachability_response.status(), StatusCode::OK); // Test Pubkeys let pubkey_request = lc.pubkeys(); let pubkey_response = handle(th.context.clone(), pubkey_request).await.unwrap(); assert_eq!(pubkey_response.status(), StatusCode::OK); let pubkeys = body_to_string(pubkey_response).await; let pubkeys_obj: lox_utils::PubKeys = serde_json::from_str(&pubkeys).unwrap(); // Test Open Invite let invite_response_str = body_to_string(invite_response).await; let response_data: lox_utils::Invite = serde_json::from_str(&invite_response_str).unwrap(); let token = match lox_utils::validate(&response_data.invite) { Ok(token) => token, Err(e) => panic!("Error: Invitation token error {:?}", e.to_string()), }; let (request, state) = lox::proto::open_invite::request(&token); let open_request = lc.openinvite(request); let open_response = handle(th.context.clone(), open_request).await.unwrap(); assert_eq!(open_response.status(), StatusCode::OK); // Test Trust Promotion let open_resp = body_to_string(open_response).await; let open_response_obj = serde_json::from_str(&open_resp).unwrap(); let lox_cred = match lox::proto::open_invite::handle_response( state, open_response_obj, &pubkeys_obj.lox_pub, ) { Ok(lox_cred) => lox_cred, Err(e) => panic!("Error: Lox credential error {:?}", e.to_string()), }; let lox_cred: lox_utils::LoxCredential = lox_utils::LoxCredential { lox_credential: lox_cred.0, bridgeline: Some(lox_cred.1), invitation: None, }; // Advance the context to a day after the credential becomes eligible to upgrade th.advance_days(31); let trust_result = match proto::trust_promotion::request( &lox_cred.lox_credential, &pubkeys_obj.lox_pub, test_today(31), ) { Ok(trust_result) => trust_result, Err(e) => panic!( "Error: Proof error from trust promotion {:?}", e.to_string() ), }; let trustpromo_request = lc.trustpromo(trust_result.0); let trustpromo_response = handle(th.context.clone(), trustpromo_request) .await .unwrap(); assert_eq!(trustpromo_response.status(), StatusCode::OK); // Test Trust Migration let trustpromo_resp = body_to_string(trustpromo_response).await; let trustpromo_response_obj = serde_json::from_str(&trustpromo_resp).unwrap(); let mig_cred = match lox::proto::trust_promotion::handle_response( trust_result.1, trustpromo_response_obj, ) { Ok(mig_cred) => mig_cred, Err(e) => panic!("Error: Migration token error {:?}", e.to_string()), }; let migration_result = match proto::migration::request( &lox_cred.lox_credential, &mig_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.migration_pub, ) { Ok(migration_result) => migration_result, Err(e) => panic!( "Error: Proof error from trust migration {:?}", e.to_string() ), }; let trustmig_request = lc.trustmigration(migration_result.0); let trustmig_response = handle(th.context.clone(), trustmig_request).await.unwrap(); assert_eq!(trustmig_response.status(), StatusCode::OK); // Test Level up let trustmig_resp = body_to_string(trustmig_response).await; let trustmig_response_obj = serde_json::from_str(&trustmig_resp).unwrap(); let level_one_cred = match lox::proto::migration::handle_response( migration_result.1, trustmig_response_obj, &pubkeys_obj.lox_pub, ) { Ok(level_one_cred) => level_one_cred, Err(e) => panic!("Error: Level one credential error {:?}", e.to_string()), }; th.advance_days(14); let new_reachability_request = lc.reachability(); let new_reachability_response = handle(th.context.clone(), new_reachability_request) .await .unwrap(); let encrypted_table = body_to_string(new_reachability_response).await; let reachability_cred: BucketReachability = lox_utils::generate_reachability_cred(&level_one_cred, encrypted_table); let level_up_result = match proto::level_up::request( &level_one_cred, &reachability_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.reachability_pub, test_today(31 + 14), ) { Ok(level_up_result) => level_up_result, Err(e) => panic!( "Error: Proof error from level up {:?}", e.to_string() ), }; let level_up_request = lc.levelup(level_up_result.0); let level_up_response = handle(th.context.clone(), level_up_request).await.unwrap(); assert_eq!(level_up_response.status(), StatusCode::OK); // Test Issue Invite let levelup_resp = body_to_string(level_up_response).await; let levelup_response_obj = serde_json::from_str(&levelup_resp).unwrap(); let level_two_cred = match lox::proto::level_up::handle_response( level_up_result.1, levelup_response_obj, &pubkeys_obj.lox_pub, ) { Ok(level_two_cred) => level_two_cred, Err(e) => panic!("Error: Level two credential error {:?}", e.to_string()), }; let new_reachability_request = lc.reachability(); let new_reachability_response = handle(th.context.clone(), new_reachability_request) .await .unwrap(); let encrypted_table = body_to_string(new_reachability_response).await; let reachability_cred: BucketReachability = lox_utils::generate_reachability_cred(&level_two_cred, encrypted_table); let issue_invite_result = match proto::issue_invite::request( &level_two_cred, &reachability_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.reachability_pub, test_today(31 + 14), ) { Ok(issue_invite_result) => issue_invite_result, Err(e) => panic!( "Error: Proof error from issue invitation {:?}", e.to_string() ), }; let issue_invite_request = lc.issueinvite(issue_invite_result.0); let issue_invite_response = handle(th.context.clone(), issue_invite_request).await.unwrap(); println!("Server response?: {:?}", issue_invite_response); assert_eq!(issue_invite_response.status(), StatusCode::OK); // Test Redeem Invite let invite_resp = body_to_string(issue_invite_response).await; let invite_response_obj = serde_json::from_str(&invite_resp).unwrap(); let issue_invite_cred = match lox::proto::issue_invite::handle_response( issue_invite_result.1, invite_response_obj, &pubkeys_obj.lox_pub, &pubkeys_obj.invitation_pub, ) { Ok(issue_invite_cred) => issue_invite_cred, Err(e) => panic!("Error: Issue invite credential error {:?}", e.to_string()), }; let new_invite = match proto::redeem_invite::request( &issue_invite_cred.1, &pubkeys_obj.invitation_pub, test_today(31 + 14), ) { Ok(new_invite) => new_invite, Err(e) => panic!( "Error: Proof error from level up {:?}", e.to_string() ), }; let new_redeem_invite_request = lc.redeeminvite(new_invite.0); let new_redeem_invite_response = handle(th.context.clone(), new_redeem_invite_request) .await .unwrap(); println!("Server response?: {:?}", new_redeem_invite_response); assert_eq!(new_redeem_invite_response.status(), StatusCode::OK); let redeemed_cred_resp = body_to_string(new_redeem_invite_response).await; let redeemed_cred_resp_obj = serde_json::from_str(&redeemed_cred_resp).unwrap(); let redeemed_cred_result = match proto::redeem_invite::handle_response( new_invite.1, redeemed_cred_resp_obj, &pubkeys_obj.lox_pub, ) { Ok(redeemed_cred_result) => redeemed_cred_result, Err(e) => panic!( "Error: Proof error from issue invitation {:?}", e.to_string() ), }; } }