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()
),
};
}
}