#include "server.h" #include "logger.h" #include "defines.h" #include "unitsManager.h" #include "weaponsManager.h" #include "scheduler.h" #include "luatools.h" #include #include #include "base64.hpp" #include using namespace std::chrono; using namespace base64; extern UnitsManager* unitsManager; extern WeaponsManager* weaponsManager; extern Scheduler* scheduler; extern json::value missionData; extern mutex mutexLock; extern string sessionHash; extern string instancePath; void handle_eptr(std::exception_ptr eptr) { try { if (eptr) { std::rethrow_exception(eptr); } } catch (const std::exception& e) { log(e.what()); } } Server::Server(lua_State* L): serverThread(nullptr), runListener(true) { } void Server::start(lua_State* L) { log("Starting RESTServer"); serverThread = new thread(&Server::task, this); } void Server::stop(lua_State* L) { log("Stopping RESTServer"); runListener = false; if (serverThread != nullptr) serverThread->join(); } void Server::handle_options(http_request request) { http_response response(status_codes::OK); response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Origin"), U("*")); response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); request.reply(response); } void Server::handle_get(http_request request) { /* Lock for thread safety */ lock_guard guard(mutexLock); milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); http_response response(status_codes::OK); string password = extractPassword(request); if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0) { std::exception_ptr eptr; try { auto answer = json::value::object(); auto path = uri::split_path(uri::decode(request.relative_uri().path())); /* If present, extract the request reference time. This is used for updates, and it specifies the last time that request has been performed */ map query = request.relative_uri().split_query(request.relative_uri().query()); unsigned long long time = 0; if (query.find(L"time") != query.end()) { try { time = stoull((*(query.find(L"time"))).second); } catch (...) { time = 0; } } if (path.size() > 0) { string URI = to_string(path[0]); /* Units data */ if (URI.compare(UNITS_URI) == 0) { unsigned long long updateTime = ms.count(); stringstream ss; ss.write((char*)&updateTime, sizeof(updateTime)); unitsManager->getUnitData(ss, time); response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); } else if (URI.compare(WEAPONS_URI) == 0) { unsigned long long updateTime = ms.count(); stringstream ss; ss.write((char*)&updateTime, sizeof(updateTime)); weaponsManager->getWeaponData(ss, time); response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); } else { /* Logs data */ if (URI.compare(LOGS_URI) == 0) { auto logs = json::value::object(); getLogsJSON(logs, time); answer[L"logs"] = logs; } /* Airbases data */ else if (URI.compare(AIRBASES_URI) == 0 && missionData.has_object_field(L"airbases")) answer[L"airbases"] = missionData[L"airbases"]; /* Bullseyes data */ else if (URI.compare(BULLSEYE_URI) == 0 && missionData.has_object_field(L"bullseyes")) answer[L"bullseyes"] = missionData[L"bullseyes"]; /* Mission data */ else if (URI.compare(MISSION_URI) == 0 && missionData.has_object_field(L"mission")) { answer[L"mission"] = missionData[L"mission"]; answer[L"mission"][L"commandModeOptions"] = scheduler->getCommandModeOptions(); /* The active mode is determined by the inserted password*/ if (password.compare(gameMasterPassword) == 0) answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Game master"); else if (password.compare(blueCommanderPassword) == 0) answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Blue commander"); else if (password.compare(redCommanderPassword) == 0) answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Red commander"); else answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Observer"); } else if (URI.compare(COMMANDS_URI) == 0 && query.find(L"commandHash") != query.end()) { answer[L"commandExecuted"] = json::value(scheduler->isCommandExecuted(to_string(query[L"commandHash"]))); } /* Common data */ answer[L"time"] = json::value::string(to_wstring(ms.count())); answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); answer[L"load"] = scheduler->getLoad(); answer[L"frameRate"] = scheduler->getFrameRate(); response.set_body(answer); } } } catch (...) { eptr = std::current_exception(); // capture } handle_eptr(eptr); } else { response = status_codes::Unauthorized; } response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Origin"), U("*")); response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); request.reply(response); } void Server::handle_request(http_request request, function action) { http_response response(status_codes::OK); //TODO: limit what a user can do depending on the password string password = extractPassword(request); if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0) { auto answer = json::value::object(); request.extract_json().then([&answer, &action](pplx::task task) { try { auto const& jvalue = task.get(); if (!jvalue.is_null()) action(jvalue, answer); } catch (http_exception const& e) { log(e.what()); } }).wait(); response.set_body(answer); } else { response = status_codes::Unauthorized; } response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Origin"), U("*")); response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); request.reply(response); } void Server::handle_put(http_request request) { string username = extractUsername(request); handle_request( request, [username](json::value const& jvalue, json::value& answer) { /* Lock for thread safety */ lock_guard guard(mutexLock); for (auto const& e : jvalue.as_object()) { auto key = e.first; auto value = e.second; std::exception_ptr eptr; try { scheduler->handleRequest(to_string(key), value, username, answer); } catch (...) { eptr = std::current_exception(); // capture } handle_eptr(eptr); } }); } string Server::extractUsername(http_request& request) { if (request.headers().has(L"Authorization")) { string authorization = to_string(request.headers().find(L"Authorization")->second); string s = "Basic "; string::size_type i = authorization.find(s); if (i != std::string::npos) authorization.erase(i, s.length()); else return ""; string decoded = from_base64(authorization); i = decoded.find(":"); if (i != string::npos && i <= decoded.length()) decoded.erase(i, decoded.length() - i); else return ""; return decoded; } else return ""; } string Server::extractPassword(http_request& request) { if (request.headers().has(L"Authorization")) { string authorization = to_string(request.headers().find(L"Authorization")->second); string s = "Basic "; string::size_type i = authorization.find(s); if (i != std::string::npos) authorization.erase(i, s.length()); else return ""; string decoded = from_base64(authorization); i = decoded.find(":"); if (i != string::npos && i+1 < decoded.length()) decoded.erase(0, i+1); else return ""; return decoded; } else return ""; } void Server::task() { string address = REST_ADDRESS; string jsonLocation = instancePath + OLYMPUS_JSON_PATH; log("Reading configuration from " + jsonLocation); std::ifstream ifstream(jsonLocation); std::stringstream ss; ss << ifstream.rdbuf(); std::error_code errorCode; json::value config = json::value::parse(ss.str(), errorCode); if (config.is_object() && config.has_object_field(L"backend") && config[L"backend"].has_string_field(L"address") && config[L"backend"].has_number_field(L"port")) { address = "http://" + to_string(config[L"backend"][L"address"]) + ":" + to_string(config[L"backend"][L"port"].as_number().to_int32()); log("Starting backend on " + address); } else log("Error reading configuration file. Starting backend on " + address); if (config.is_object() && config.has_object_field(L"authentication")) { if (config[L"authentication"].has_string_field(L"gameMasterPassword")) gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]); if (config[L"authentication"].has_string_field(L"blueCommanderPassword")) blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]); if (config[L"authentication"].has_string_field(L"redCommanderPassword")) redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]); } else log("Error reading configuration file. No password set."); http_listener listener(to_wstring(address + "/" + REST_URI)); std::function handle_options = std::bind(&Server::handle_options, this, std::placeholders::_1); std::function handle_get = std::bind(&Server::handle_get, this, std::placeholders::_1); std::function handle_put = std::bind(&Server::handle_put, this, std::placeholders::_1); listener.support(methods::OPTIONS, handle_options); listener.support(methods::GET, handle_get); listener.support(methods::PUT, handle_put); try { listener.open() .then([&listener]() {log("RESTServer starting to listen"); }) .wait(); while (runListener) { Sleep(1000); }; listener.close() .then([&listener]() {log("RESTServer stopping connections"); }) .wait(); log("RESTServer stopped listening"); } catch (exception const& e) { log(e.what()); } }