From 84f934f39d8d54d4d782aac27efcbbbed7c538a5 Mon Sep 17 00:00:00 2001 From: EntireTwix Date: Thu, 10 Jun 2021 01:57:18 -0700 Subject: [PATCH] :sparkles: Split .hpp into .h and .cpp --- CMakeLists.txt | 10 +- README.md | 3 +- include/bank.h | 61 +++++++ include/bank.hpp | 329 -------------------------------------- include/bank_f.h | 55 +++++++ include/bank_f.hpp | 155 ------------------ include/log.h | 13 ++ include/log.hpp | 40 ----- include/transaction.h | 15 ++ include/transactions.hpp | 24 --- include/user.h | 20 +++ include/user.hpp | 62 ------- main.cpp | 6 +- src/bank.cpp | 295 ++++++++++++++++++++++++++++++++++ src/bank_f.cpp | 122 ++++++++++++++ src/log.cpp | 31 ++++ src/transaction.cpp | 15 ++ src/user.cpp | 51 ++++++ {include => src}/xxhash.c | 0 19 files changed, 692 insertions(+), 615 deletions(-) create mode 100644 include/bank.h delete mode 100644 include/bank.hpp create mode 100644 include/bank_f.h delete mode 100644 include/bank_f.hpp create mode 100644 include/log.h delete mode 100644 include/log.hpp create mode 100644 include/transaction.h delete mode 100644 include/transactions.hpp create mode 100644 include/user.h delete mode 100644 include/user.hpp create mode 100644 src/bank.cpp create mode 100644 src/bank_f.cpp create mode 100644 src/log.cpp create mode 100644 src/transaction.cpp create mode 100644 src/user.cpp rename {include => src}/xxhash.c (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a512151..6e74ca3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,15 @@ set(CMAKE_CXX_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_RELEASE "-O3") find_package(Threads) -add_executable(${PROJECT_NAME} main.cpp include/xxhash.c) +add_executable(${PROJECT_NAME} main.cpp ) +target_sources(${PROJECT_NAME} PRIVATE + src/bank_f.cpp + src/bank.cpp + src/log.cpp + src/transaction.cpp + src/user.cpp + src/xxhash.c +) target_include_directories(${PROJECT_NAME} PUBLIC include) target_include_directories(${PROJECT_NAME} PUBLIC third_party) diff --git a/README.md b/README.md index c304dff..56be0af 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ CCash solves these issues and adds a level of abstraction, the main philosophy o ## Build -drogon depedencies - +drogon depedencies (varies by OS/distro) ``` sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev ``` diff --git a/include/bank.h b/include/bank.h new file mode 100644 index 0000000..f1e6929 --- /dev/null +++ b/include/bank.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include +#include +#include "error_responses.hpp" +#include "parallel-hashmap/parallel_hashmap/phmap.h" +#include "user.h" + +class Bank +{ +private: + phmap::parallel_flat_hash_map< + std::string, User, + phmap::priv::hash_default_hash, + phmap::priv::hash_default_eq, + phmap::priv::Allocator>, + 4UL, + std::mutex> + users; + + /** + * @brief size_l should be grabbed if the operation MODIFIES the size (shared), this is so that when save claims unique + * + */ + std::shared_mutex size_l; + + /** + * @brief send_funds_l should be grabbed if balances are being MODIFIED (shared) or if an operation needs to READ without the intermediary states that sendfunds has (unique) + * + */ + std::shared_mutex send_funds_l; + +public: + std::string admin_pass; + + int_fast8_t AddUser(const std::string &name, std::string &&init_pass); + int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass); + + int_fast8_t DelUser(const std::string &name, const std::string &attempt); + int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt); + + int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt); + + bool Contains(const std::string &name) const; + bool AdminVerifyPass(const std::string &attempt); + + int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount); + int_fast64_t GetBal(const std::string &name) const; + + int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const; + int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass); + + Json::Value GetLogs(const std::string &name, const std::string &attempt); + + void Save(); + + //NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS + void Load(); +}; + +//TODO make branchless \ No newline at end of file diff --git a/include/bank.hpp b/include/bank.hpp deleted file mode 100644 index b95ce85..0000000 --- a/include/bank.hpp +++ /dev/null @@ -1,329 +0,0 @@ -#pragma once -#include -#include -#include "xxhash.h" -#include "error_responses.hpp" -#include "parallel-hashmap/parallel_hashmap/phmap.h" -#include "user.hpp" - -class -{ -private: - phmap::parallel_flat_hash_map< - std::string, User, - phmap::priv::hash_default_hash, - phmap::priv::hash_default_eq, - phmap::priv::Allocator>, - 4UL, - std::mutex> - users; - - /** - * @brief size_l should be grabbed if the operation MODIFIES the size (shared), this is so that when save claims unique - * - */ - std::shared_mutex size_l; - - /** - * @brief send_funds_l should be grabbed if balances are being MODIFIED (shared) or if an operation needs to READ without the intermediary states that sendfunds has (unique) - * - */ - std::shared_mutex send_funds_l; - -public: - std::string admin_pass; - - int_fast8_t AddUser(const std::string &name, std::string &&init_pass) - { - if (name.size() > max_name_size) - { - return ErrorResponse::NameTooLong; - } - { - std::shared_lock lock{size_l}; - if (!users.try_emplace_l( - name, [](User &) {}, std::move(init_pass))) - { - return ErrorResponse::UserAlreadyExists; - } - else - { - return true; - } - } - } - int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) - { - if (name.size() > max_name_size) - { - return ErrorResponse::NameTooLong; - } - if (admin_pass != attempt) - { - return ErrorResponse::WrongAdminPassword; - } - { - std::shared_lock lock{size_l}; - if (!users.try_emplace_l( - name, [](User &) {}, init_bal, std::move(init_pass))) - { - return ErrorResponse::UserAlreadyExists; - } - else - { - return true; - } - } - } - - int_fast8_t DelUser(const std::string &name, const std::string &attempt) - { - std::shared_lock lock{size_l}; - bool state = false; - if (!users.erase_if(name, [&state, &attempt](User &u) { return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); })) - { - return ErrorResponse::UserNotFound; - } - else - { - return state * ErrorResponse::WrongPassword; - } - } - int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt) - { - std::shared_lock lock{size_l}; - bool state = false; - if (!users.erase_if(name, [&state, this, &attempt](const User &) { return state = (admin_pass == attempt); })) - { - return ErrorResponse::UserNotFound; - } - else - { - return state * ErrorResponse::WrongAdminPassword; - } - } - - int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) - { - //cant send money to self, from self or amount is 0 - if (a_name == b_name || !amount) - { - return ErrorResponse::InvalidRequest; - } - - int_fast8_t state = false; - { - std::shared_lock lock{send_funds_l}; //because SendFunds requires 3 locking operations - if (users.modify_if(a_name, [&state, amount, &attempt](User &a) { - //if A exists, A can afford it, and A's password matches - if (a.balance < amount) - { - state = ErrorResponse::InsufficientFunds; - } - else - { - if (a.password != XXH3_64bits(attempt.data(), attempt.size())) - { - state = ErrorResponse::WrongPassword; - } - else - { - a.balance -= amount; - state = true; - } - } - })) - { - return ErrorResponse::UserNotFound; - } - else - { - if (!state) - { - return state; - } - else - { - //if B doesnt exist - if (!users.modify_if(b_name, [amount](User &b) { - b.balance += amount; - })) - { - //attempt to refund if A exist - users.modify_if(a_name, [amount](User &a) { - a.balance += amount; - }); - return ErrorResponse::UserNotFound; //because had to refund transaction - } - else - { - if constexpr (max_log_size) - { - Transaction temp(a_name, b_name, amount); - Transaction temp2 = temp; - users.modify_if(a_name, [&temp](User &a) { - a.log.AddTrans(std::move(temp)); - }); - users.modify_if(b_name, [&temp2](User &b) { - b.log.AddTrans(std::move(temp2)); - }); - } - return true; - } - } - } - } - } - - bool Contains(const std::string &name) const - { - return users.contains(name); - } - bool AdminVerifyPass(const std::string &attempt) - { - return (admin_pass != attempt); - } - - int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount) - { - if (admin_pass != attempt) - { - return ErrorResponse::WrongAdminPassword; - } - else - { - if (!users.modify_if(name, [amount](User &u) { - u.balance = amount; - })) - { - return ErrorResponse::UserNotFound; - } - else - { - return true; - } - } - } - int_fast64_t GetBal(const std::string &name) const - { - int_fast64_t res = ErrorResponse::UserNotFound; - users.if_contains(name, [&res](const User &u) { - res = u.balance; - }); - return res; - } - - int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const - { - int_fast8_t res = ErrorResponse::UserNotFound; - users.if_contains(name, [&res, &attempt](const User &u) { - res = u.password == XXH3_64bits(attempt.data(), attempt.size()); - }); - return res; - } - int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) - { - int_fast8_t res = ErrorResponse::UserNotFound; - users.modify_if(name, [&res, &attempt, &new_pass](User &u) { - if (u.password != XXH3_64bits(attempt.data(), attempt.size())) - { - res = ErrorResponse::WrongPassword; - } - else - { - u.password = XXH3_64bits(new_pass.data(), new_pass.size()); - } - }); - return res; - } - - Json::Value GetLogs(const std::string &name, const std::string &attempt) - { - Json::Value res; - if (!users.if_contains(name, [&res, &attempt](const User &u) { - if (u.password != XXH3_64bits(attempt.data(), attempt.size())) - { - res = ErrorResponse::WrongPassword; - } - else - { - Json::Value temp; - for (uint32_t i = u.log.data.size(); i > 0; --i) - { - temp[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; - temp[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; - temp[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; - temp[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; - } - res = std::move(temp); - } - })) - { - return ErrorResponse::UserNotFound; - } - return res; - } - - void Save() - { - Json::Value temp; - - //loading info into json temp - { - std::scoped_lock lock{size_l, send_funds_l}; - for (const auto &u : users) - { - //we know it contains this key but we call this func to grab mutex - users.if_contains(u.first, [&temp, &u](const User &u_val) { - temp[u.first] = u_val.Serialize(); - }); - } - } - if (!temp.isNull()) - { - std::ofstream user_save("../users.json"); - Json::StreamWriterBuilder builder; - const std::unique_ptr writer(builder.newStreamWriter()); - writer->write(temp, &user_save); - user_save.close(); - } - else - { - throw std::invalid_argument("Saving Failed\n"); - } - } - - //NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS - void Load() - { - Json::CharReaderBuilder builder; - - Json::Value temp; - std::ifstream user_save("../users.json"); - builder["collectComments"] = true; - JSONCPP_STRING errs; - if (!parseFromStream(builder, user_save, &temp, &errs)) - { - std::cerr << errs << '\n'; - user_save.close(); - throw std::invalid_argument("Parsing Failed\n"); - } - else - { - user_save.close(); - for (const auto &u : temp.getMemberNames()) - { - if constexpr (max_log_size) - { - users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64()), temp[u]["log"]); - } - else - { - users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64())); - } - } - } - } -} bank; - -//TODO make branchless \ No newline at end of file diff --git a/include/bank_f.h b/include/bank_f.h new file mode 100644 index 0000000..aeb6d5f --- /dev/null +++ b/include/bank_f.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include "bank.h" + +using namespace drogon; + +#define req_args const HttpRequestPtr &req, std::function &&callback + +class BankF : public HttpController +{ + Bank &bank; + +public: + BankF(Bank *b); + void Help(req_args) const; + void Close(req_args) const; + void AddUser(req_args, std::string &&name) const; + void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const; + void DelUser(req_args, const std::string &name) const; + void AdminDelUser(req_args, const std::string &name) const; + void SendFunds(req_args, const std::string name, const std::string to, uint32_t amount) const; + void ChangePassword(req_args, const std::string &name) const; + void Contains(req_args, const std::string &name) const; + void GetBal(req_args, const std::string &name) const; + void VerifyPassword(req_args, const std::string &name) const; + void SetBal(req_args, const std::string &name, uint32_t amount) const; + void AdminVerifyPass(req_args); + void GetLog(req_args, const std::string &name); + + METHOD_LIST_BEGIN + + //Usage + METHOD_ADD(BankF::GetBal, "/{name}/bal", Get, Options); + METHOD_ADD(BankF::GetLog, "/{name}/log", Get, Options); + METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}/amount={amount}", Post, Options); + METHOD_ADD(BankF::VerifyPassword, "/{name}/pass/verify", Get, Options); + + //Meta Usage + METHOD_ADD(BankF::ChangePassword, "/{name}/pass/change", Patch, Options); + METHOD_ADD(BankF::SetBal, "/admin/{name}/bal/amount={amount}", Patch, Options); + + //System Usage + METHOD_ADD(BankF::Help, "/help", Get, Options); + METHOD_ADD(BankF::Close, "/admin/close", Post, Options); + METHOD_ADD(BankF::Contains, "/contains/{name}", Get, Options); + METHOD_ADD(BankF::AdminVerifyPass, "/admin/verify", Get, Options); + + //User Managment + METHOD_ADD(BankF::AddUser, "/user/{name}", Post, Options); + METHOD_ADD(BankF::AdminAddUser, "/admin/user/{name}?init_bal={init_bal}", Post, Options); + METHOD_ADD(BankF::DelUser, "/user/{name}", Delete, Options); + METHOD_ADD(BankF::AdminDelUser, "/admin/user/{name}", Delete, Options); + + METHOD_LIST_END +}; \ No newline at end of file diff --git a/include/bank_f.hpp b/include/bank_f.hpp deleted file mode 100644 index c318865..0000000 --- a/include/bank_f.hpp +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once -#include -#include "bank.hpp" - -using namespace drogon; - -#define req_args const HttpRequestPtr &req, std::function &&callback -#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonReturn(V))); -#define INLINE __attribute__((always_inline)) inline -#define GEN_BODY \ - const auto temp_req = req->getJsonObject(); \ - const auto body = temp_req ? *temp_req : Json::Value(); -#define PASS_HEADER std::string pass = req->getHeader("Password"); - -template -INLINE Json::Value JsonReturn(T &&val) -{ - Json::Value res; - if constexpr (std::is_same_v) - { - res["value"] = (int)val; //becuase of json lib interpreting 67 as 'A' for example - } - else if constexpr (std::is_same_v) - { - res["value"] = (Json::Int64)val; - } - else - { - res["value"] = val; - } - return res; -} - -class BankF : public HttpController -{ -public: - void Help(req_args) const - { - auto resp = HttpResponse::newHttpResponse(); - auto handlerInfo = app().getHandlersInfo(); - resp->setBody("

Error Responses

# meaning
-1 UserNotFound
-2 WrongPassword
-3 InvalidRequest
-4 WrongAdminPassword
-5 NameTooLong
-6 UserAlreadyExists
-7 InsufficientFunds

Things of Note

  • all endpoints respond with JSON file type
  • "A" denotes requiring Authentication in the form of a header titled "Password"

Usage

Name Path Method A Description
GetBal /{name}/bal GET true returns the balance of a given user {name}
GetLog /{name}/log GET true returns a list of last n number of transactions (a configurable amount) of a given user {name}
SendFunds /{name}/send/{to}/amount={amount} POST false sends {amount} from user {name} to user {to}
VerifyPassword /{name}/pass/verify GET true returns true or false depending on if the supplied user {name}'s password matches the password supplied in the header

Meta Usage

Name Path Method A Description
ChangePassword /{name}/pass/change PATCH true if the password supplied in the header matches the user {name}'s password, the user’s password is changed to the one given in the body
SetBal /admin/{name}/bal/amount={amount} PATCH true sets the balance of a give user {name} if the supplied password matches the admin password

System Usage

Name Path Method A Description
Help /help GET false the page you’re looking at right now!
Close /admin/close POST true saves and then closes the program if the supplied password matches the admin password
Contains /contains/{name} GET false returns true or false depending on if the supplied user {name} exists
AdminVerifyPass /admin/verify GET true returns true or false depending on if the password supplied in the header matches the admin password

User Management

Name Path Method A Description
AddUser /user/{name} POST true registers a user with the name {name}, balance of 0 and a password of the password supplied in the header
AdminAddUser /admin/user/{name}?init_bal={init_bal} POST true if the password supplied in the header matches the admin password, then it registers a user with the name {name}, balance of init_bal and a password supplied by the body of the request
DelUser /user/{name} DELETE true if the password supplied in the header matches the user {name}'s password, then the user is deleted
AdminDelUser /admin/user/{name} DELETE true if the password supplied in the header matches the admin password, then the user is deleted
"); - resp->setExpiredTime(0); - callback(resp); - } - void Close(req_args) const - { - PASS_HEADER - bool res; - if (pass == bank.admin_pass) - { - bank.Save(); - - res = true; - app().quit(); - } - else - { - res = false; - } - JSON(res); - } - void AddUser(req_args, std::string &&name) const - { - PASS_HEADER - JSON(bank.AddUser(std::move(name), std::move(pass))); - } - void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const - { - PASS_HEADER - JSON(bank.AdminAddUser(pass, std::move(name), init_bal, std::string(req->getBody()))); - } - void DelUser(req_args, const std::string &name) const - { - PASS_HEADER - JSON(bank.DelUser(name, pass)); - } - void AdminDelUser(req_args, const std::string &name) const - { - PASS_HEADER - JSON(bank.AdminDelUser(name, pass)); - } - void SendFunds(req_args, const std::string name, const std::string to, uint32_t amount) const - { - PASS_HEADER - JSON(bank.SendFunds(name, to, amount, pass)); - } - void ChangePassword(req_args, const std::string &name) const - { - PASS_HEADER - JSON(bank.ChangePassword(name, pass, std::string(req->getBody()))); - } - void Contains(req_args, const std::string &name) const - { - JSON(bank.Contains(name)); - } - void GetBal(req_args, const std::string &name) const - { - JSON(bank.GetBal(name)); - } - void VerifyPassword(req_args, const std::string &name) const - { - PASS_HEADER - JSON(bank.VerifyPassword(name, pass)); - } - void SetBal(req_args, const std::string &name, uint32_t amount) const - { - PASS_HEADER - JSON(bank.SetBal(name, pass, amount)); - } - void AdminVerifyPass(req_args) - { - PASS_HEADER - JSON(bank.AdminVerifyPass(pass)); - } - void GetLog(req_args, const std::string &name) - { - if constexpr (max_log_size) - { - PASS_HEADER - JSON(bank.GetLogs(name, pass)); - } - else - { - auto resp = HttpResponse::newHttpJsonResponse(JsonReturn("Logs are Disabled")); - resp->setExpiredTime(0); //cached forever - callback(resp); - } - } - - METHOD_LIST_BEGIN - - //Usage - METHOD_ADD(BankF::GetBal, "/{name}/bal", Get, Options); - METHOD_ADD(BankF::GetLog, "/{name}/log", Get, Options); - METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}/amount={amount}", Post, Options); - METHOD_ADD(BankF::VerifyPassword, "/{name}/pass/verify", Get, Options); - - //Meta Usage - METHOD_ADD(BankF::ChangePassword, "/{name}/pass/change", Patch, Options); - METHOD_ADD(BankF::SetBal, "/admin/{name}/bal/amount={amount}", Patch, Options); - - //System Usage - METHOD_ADD(BankF::Help, "/help", Get, Options); - METHOD_ADD(BankF::Close, "/admin/close", Post, Options); - METHOD_ADD(BankF::Contains, "/contains/{name}", Get, Options); - METHOD_ADD(BankF::AdminVerifyPass, "/admin/verify", Get, Options); - - //User Managment - METHOD_ADD(BankF::AddUser, "/user/{name}", Post, Options); - METHOD_ADD(BankF::AdminAddUser, "/admin/user/{name}?init_bal={init_bal}", Post, Options); - METHOD_ADD(BankF::DelUser, "/user/{name}", Delete, Options); - METHOD_ADD(BankF::AdminDelUser, "/admin/user/{name}", Delete, Options); - - METHOD_LIST_END -}; \ No newline at end of file diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..d7ff7dd --- /dev/null +++ b/include/log.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include +#include "consts.hpp" +#include "transaction.h" + +struct Log +{ + std::vector data; + void AddTrans(Transaction &&t); + Json::Value Serialize() const; +}; diff --git a/include/log.hpp b/include/log.hpp deleted file mode 100644 index 2fbaa5f..0000000 --- a/include/log.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include -#include "consts.hpp" -#include "transactions.hpp" - -struct Log -{ - std::vector data; - void AddTrans(Transaction &&t) - { - if (data.size() == max_log_size) // If we hit the max size - { - for (uint32_t i = 1; i < data.size(); i++) // Make room at the back - { - std::cout << i << '\n'; - data[i - 1] = std::move(data[i]); // Shifts everything left - } - data[data.size() - 1] = std::move(t); // Place new in opened spot - return; - } - else if (data.size() == data.capacity()) // If we haven't hit the max but hit capacity - { - data.reserve(data.capacity() + pre_log_size); // Reserve more memory - } - data.push_back(std::move(t)); // In either case we have space under max length, move to new spot - } - Json::Value Serialize() const - { - Json::Value res; - for (uint32_t i = 0; i < data.size(); ++i) - { - res[i]["to"] = data[i].to; - res[i]["from"] = data[i].from; - res[i]["amount"] = (Json::UInt)data[i].amount; - res[i]["time"] = (Json::UInt64)data[i].time; - } - return res; - } -}; diff --git a/include/transaction.h b/include/transaction.h new file mode 100644 index 0000000..a82115b --- /dev/null +++ b/include/transaction.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +struct Transaction +{ + std::string from = "", to = ""; + uint32_t amount = 0; + uint64_t time = 0; + + Transaction(); + Transaction(std::string from_str, std::string to_str, uint32_t amount, uint64_t time); + Transaction(std::string from_str, std::string to_str, uint32_t amount); +}; diff --git a/include/transactions.hpp b/include/transactions.hpp deleted file mode 100644 index 996c3de..0000000 --- a/include/transactions.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include -#include - -struct Transaction -{ - std::string from = "", to = ""; - uint32_t amount = 0; - uint64_t time = 0; - - Transaction() = default; - Transaction(std::string from_str, std::string to_str, uint32_t amount, uint64_t time) : amount(amount), time(time) - { - from = std::move(from_str); - to = std::move(to_str); - } - Transaction(std::string from_str, std::string to_str, uint32_t amount) : amount(amount) - { - using namespace std::chrono; - from = std::move(from_str); - to = std::move(to_str); - time = duration_cast(system_clock::now().time_since_epoch()).count(); - } -}; diff --git a/include/user.h b/include/user.h new file mode 100644 index 0000000..81af2b2 --- /dev/null +++ b/include/user.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include +#include "xxhash.h" +#include "log.h" + +struct User +{ + uint32_t balance = 0; + uint64_t password; + Log log; + + User(std::string &&init_pass); + User(uint32_t init_bal, std::string &&init_pass); + User(uint32_t init_bal, uint64_t init_pass); + User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j); + + Json::Value Serialize() const; +}; diff --git a/include/user.hpp b/include/user.hpp deleted file mode 100644 index bbdb10f..0000000 --- a/include/user.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include -#include -#include -#include "log.hpp" - -struct User -{ - uint32_t balance = 0; - uint64_t password; - Log log; - - /** - * @brief User constructor - * - * @param init_pass initial password - */ - User(std::string &&init_pass) : password(XXH3_64bits(init_pass.data(), init_pass.size())) {} - - /** - * @brief User Constructor for admins - * - * @param init_bal initial balance - * @param init_pass initial password - */ - User(uint32_t init_bal, std::string &&init_pass) : balance(init_bal), password(XXH3_64bits(init_pass.data(), init_pass.size())) {} - - /** - * @brief User Constructor for loading - * - * @param init_bal - * @param init_pass - */ - User(uint32_t init_bal, uint64_t init_pass) : balance(init_bal), password(init_pass) {} - User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j) : balance(init_bal), password(init_pass) - { - if (log_j.size()) - { - log.data.reserve(std::min(pre_log_size * ((log_j.size() / pre_log_size) + 1), max_log_size)); - for (uint32_t i = (log_j.size() - max_log_size) * (log_j.size() > max_log_size); i < log_j.size(); i++) - { - log.data.push_back(std::move(Transaction( - log_j[i]["from"].asCString(), - log_j[i]["to"].asCString(), - log_j[i]["amount"].asUInt(), - log_j[i]["time"].asUInt64()))); - } - } - } - - Json::Value Serialize() const - { - Json::Value res; - res["balance"] = (Json::UInt)balance; - res["password"] = (Json::UInt64)password; - if constexpr (max_log_size) - { - res["log"] = log.Serialize(); - } - return res; - } -}; diff --git a/main.cpp b/main.cpp index a013878..d8f7283 100644 --- a/main.cpp +++ b/main.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "bank_f.hpp" +#include "bank_f.h" #include #include @@ -13,6 +13,8 @@ using namespace std::chrono; using namespace drogon; +static Bank bank; + void SaveSig(int s) { bank.Save(); @@ -66,7 +68,7 @@ int main(int argc, char **argv) }).detach(); } - auto API = std::make_shared(); + auto API = std::make_shared(&bank); app().registerPostHandlingAdvice( [](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) { resp->addHeader("Access-Control-Allow-Origin", "*"); diff --git a/src/bank.cpp b/src/bank.cpp new file mode 100644 index 0000000..b1acba4 --- /dev/null +++ b/src/bank.cpp @@ -0,0 +1,295 @@ +#include "bank.h" + +int_fast8_t Bank::AddUser(const std::string &name, std::string &&init_pass) +{ + if (name.size() > max_name_size) + { + return ErrorResponse::NameTooLong; + } + { + std::shared_lock lock{size_l}; + if (!users.try_emplace_l( + name, [](User &) {}, std::move(init_pass))) + { + return ErrorResponse::UserAlreadyExists; + } + else + { + return true; + } + } +} +int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) +{ + if (name.size() > max_name_size) + { + return ErrorResponse::NameTooLong; + } + if (admin_pass != attempt) + { + return ErrorResponse::WrongAdminPassword; + } + { + std::shared_lock lock{size_l}; + if (!users.try_emplace_l( + name, [](User &) {}, init_bal, std::move(init_pass))) + { + return ErrorResponse::UserAlreadyExists; + } + else + { + return true; + } + } +} + +int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) +{ + std::shared_lock lock{size_l}; + bool state = false; + if (!users.erase_if(name, [&state, &attempt](User &u) { return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); })) + { + return ErrorResponse::UserNotFound; + } + else + { + return state * ErrorResponse::WrongPassword; + } +} +int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt) +{ + std::shared_lock lock{size_l}; + bool state = false; + if (!users.erase_if(name, [this, &state, &attempt](const User &) { return state = (admin_pass == attempt); })) + { + return ErrorResponse::UserNotFound; + } + else + { + return state * ErrorResponse::WrongAdminPassword; + } +} + +int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) +{ + //cant send money to self, from self or amount is 0 + if (a_name == b_name || !amount) + { + return ErrorResponse::InvalidRequest; + } + + int_fast8_t state = false; + { + std::shared_lock lock{send_funds_l}; //because SendFunds requires 3 locking operations + if (users.modify_if(a_name, [&state, amount, &attempt](User &a) { + //if A exists, A can afford it, and A's password matches + if (a.balance < amount) + { + state = ErrorResponse::InsufficientFunds; + } + else + { + if (a.password != XXH3_64bits(attempt.data(), attempt.size())) + { + state = ErrorResponse::WrongPassword; + } + else + { + a.balance -= amount; + state = true; + } + } + })) + { + return ErrorResponse::UserNotFound; + } + else + { + if (!state) + { + return state; + } + else + { + //if B doesnt exist + if (!users.modify_if(b_name, [amount](User &b) { + b.balance += amount; + })) + { + //attempt to refund if A exist + users.modify_if(a_name, [amount](User &a) { + a.balance += amount; + }); + return ErrorResponse::UserNotFound; //because had to refund transaction + } + else + { + if constexpr (max_log_size) + { + Transaction temp(a_name, b_name, amount); + Transaction temp2 = temp; + users.modify_if(a_name, [&temp](User &a) { + a.log.AddTrans(std::move(temp)); + }); + users.modify_if(b_name, [&temp2](User &b) { + b.log.AddTrans(std::move(temp2)); + }); + } + return true; + } + } + } + } +} + +bool Bank::Contains(const std::string &name) const +{ + return users.contains(name); +} +bool Bank::AdminVerifyPass(const std::string &attempt) +{ + return (admin_pass != attempt); +} + +int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount) +{ + if (admin_pass != attempt) + { + return ErrorResponse::WrongAdminPassword; + } + else + { + if (!users.modify_if(name, [amount](User &u) { + u.balance = amount; + })) + { + return ErrorResponse::UserNotFound; + } + else + { + return true; + } + } +} +int_fast64_t Bank::GetBal(const std::string &name) const +{ + int_fast64_t res = ErrorResponse::UserNotFound; + users.if_contains(name, [&res](const User &u) { + res = u.balance; + }); + return res; +} + +int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const +{ + int_fast8_t res = ErrorResponse::UserNotFound; + users.if_contains(name, [&res, &attempt](const User &u) { + res = u.password == XXH3_64bits(attempt.data(), attempt.size()); + }); + return res; +} +int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) +{ + int_fast8_t res = ErrorResponse::UserNotFound; + users.modify_if(name, [&res, &attempt, &new_pass](User &u) { + if (u.password != XXH3_64bits(attempt.data(), attempt.size())) + { + res = ErrorResponse::WrongPassword; + } + else + { + u.password = XXH3_64bits(new_pass.data(), new_pass.size()); + } + }); + return res; +} + +Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) +{ + Json::Value res; + if (!users.if_contains(name, [&res, &attempt](const User &u) { + if (u.password != XXH3_64bits(attempt.data(), attempt.size())) + { + res = ErrorResponse::WrongPassword; + } + else + { + Json::Value temp; + for (uint32_t i = u.log.data.size(); i > 0; --i) + { + temp[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; + temp[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; + temp[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; + temp[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; + } + res = std::move(temp); + } + })) + { + return ErrorResponse::UserNotFound; + } + return res; +} + +void Bank::Save() +{ + Json::Value temp; + + //loading info into json temp + { + std::scoped_lock lock{size_l, send_funds_l}; + for (const auto &u : users) + { + //we know it contains this key but we call this func to grab mutex + users.if_contains(u.first, [&temp, &u](const User &u_val) { + temp[u.first] = u_val.Serialize(); + }); + } + } + if (!temp.isNull()) + { + std::ofstream user_save("../users.json"); + Json::StreamWriterBuilder builder; + const std::unique_ptr writer(builder.newStreamWriter()); + writer->write(temp, &user_save); + user_save.close(); + } + else + { + throw std::invalid_argument("Saving Failed\n"); + } +} + +//NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS +void Bank::Load() +{ + Json::CharReaderBuilder builder; + + Json::Value temp; + std::ifstream user_save("../users.json"); + builder["collectComments"] = true; + JSONCPP_STRING errs; + if (!parseFromStream(builder, user_save, &temp, &errs)) + { + std::cerr << errs << '\n'; + user_save.close(); + throw std::invalid_argument("Parsing Failed\n"); + } + else + { + user_save.close(); + for (const auto &u : temp.getMemberNames()) + { + if constexpr (max_log_size) + { + users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64()), temp[u]["log"]); + } + else + { + users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64())); + } + } + } +} + +//TODO make branchless diff --git a/src/bank_f.cpp b/src/bank_f.cpp new file mode 100644 index 0000000..38277e1 --- /dev/null +++ b/src/bank_f.cpp @@ -0,0 +1,122 @@ +#include "bank_f.h" + +#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonReturn(V))); +#define INLINE __attribute__((always_inline)) inline +#define GEN_BODY \ + const auto temp_req = req->getJsonObject(); \ + const auto body = temp_req ? *temp_req : Json::Value(); +#define PASS_HEADER std::string pass = req->getHeader("Password"); + +template +INLINE Json::Value JsonReturn(T &&val) +{ + Json::Value res; + if constexpr (std::is_same_v) + { + res["value"] = (int)val; //becuase of json lib interpreting 67 as 'A' for example + } + else if constexpr (std::is_same_v) + { + res["value"] = (Json::Int64)val; + } + else + { + res["value"] = val; + } + return res; +} + +BankF::BankF(Bank *b) : bank(*b) {} + +void BankF::Help(req_args) const +{ + auto resp = HttpResponse::newHttpResponse(); + auto handlerInfo = app().getHandlersInfo(); + resp->setBody("

Error Responses

# meaning
-1 UserNotFound
-2 WrongPassword
-3 InvalidRequest
-4 WrongAdminPassword
-5 NameTooLong
-6 UserAlreadyExists
-7 InsufficientFunds

Things of Note

  • all endpoints respond with JSON file type
  • "A" denotes requiring Authentication in the form of a header titled "Password"

Usage

Name Path Method A Description
GetBal /{name}/bal GET true returns the balance of a given user {name}
GetLog /{name}/log GET true returns a list of last n number of transactions (a configurable amount) of a given user {name}
SendFunds /{name}/send/{to}/amount={amount} POST false sends {amount} from user {name} to user {to}
VerifyPassword /{name}/pass/verify GET true returns true or false depending on if the supplied user {name}'s password matches the password supplied in the header

Meta Usage

Name Path Method A Description
ChangePassword /{name}/pass/change PATCH true if the password supplied in the header matches the user {name}'s password, the user’s password is changed to the one given in the body
SetBal /admin/{name}/bal/amount={amount} PATCH true sets the balance of a give user {name} if the supplied password matches the admin password

System Usage

Name Path Method A Description
Help /help GET false the page you’re looking at right now!
Close /admin/close POST true saves and then closes the program if the supplied password matches the admin password
Contains /contains/{name} GET false returns true or false depending on if the supplied user {name} exists
AdminVerifyPass /admin/verify GET true returns true or false depending on if the password supplied in the header matches the admin password

User Management

Name Path Method A Description
AddUser /user/{name} POST true registers a user with the name {name}, balance of 0 and a password of the password supplied in the header
AdminAddUser /admin/user/{name}?init_bal={init_bal} POST true if the password supplied in the header matches the admin password, then it registers a user with the name {name}, balance of init_bal and a password supplied by the body of the request
DelUser /user/{name} DELETE true if the password supplied in the header matches the user {name}'s password, then the user is deleted
AdminDelUser /admin/user/{name} DELETE true if the password supplied in the header matches the admin password, then the user is deleted
"); + resp->setExpiredTime(0); + callback(resp); +} +void BankF::Close(req_args) const +{ + PASS_HEADER + bool res; + if (pass == bank.admin_pass) + { + bank.Save(); + + res = true; + app().quit(); + } + else + { + res = false; + } + JSON(res); +} +void BankF::AddUser(req_args, std::string &&name) const +{ + PASS_HEADER + JSON(bank.AddUser(std::move(name), std::move(pass))); +} +void BankF::AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const +{ + PASS_HEADER + JSON(bank.AdminAddUser(pass, std::move(name), init_bal, std::string(req->getBody()))); +} +void BankF::DelUser(req_args, const std::string &name) const +{ + PASS_HEADER + JSON(bank.DelUser(name, pass)); +} +void BankF::AdminDelUser(req_args, const std::string &name) const +{ + PASS_HEADER + JSON(bank.AdminDelUser(name, pass)); +} +void BankF::SendFunds(req_args, const std::string name, const std::string to, uint32_t amount) const +{ + PASS_HEADER + JSON(bank.SendFunds(name, to, amount, pass)); +} +void BankF::ChangePassword(req_args, const std::string &name) const +{ + PASS_HEADER + JSON(bank.ChangePassword(name, pass, std::string(req->getBody()))); +} +void BankF::Contains(req_args, const std::string &name) const +{ + JSON(bank.Contains(name)); +} +void BankF::GetBal(req_args, const std::string &name) const +{ + JSON(bank.GetBal(name)); +} +void BankF::VerifyPassword(req_args, const std::string &name) const +{ + PASS_HEADER + JSON(bank.VerifyPassword(name, pass)); +} +void BankF::SetBal(req_args, const std::string &name, uint32_t amount) const +{ + PASS_HEADER + JSON(bank.SetBal(name, pass, amount)); +} +void BankF::AdminVerifyPass(req_args) +{ + PASS_HEADER + JSON(bank.AdminVerifyPass(pass)); +} +void BankF::GetLog(req_args, const std::string &name) +{ + if constexpr (max_log_size) + { + PASS_HEADER + JSON(bank.GetLogs(name, pass)); + } + else + { + auto resp = HttpResponse::newHttpJsonResponse(JsonReturn("Logs are Disabled")); + resp->setExpiredTime(0); //cached forever + callback(resp); + } +} \ No newline at end of file diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..145f329 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,31 @@ +#include "log.h" + +void Log::AddTrans(Transaction &&t) +{ + if (data.size() == max_log_size) // If we hit the max size + { + for (uint32_t i = 1; i < data.size(); i++) // Make room at the back + { + data[i - 1] = std::move(data[i]); // Shifts everything left + } + data[data.size() - 1] = std::move(t); // Place new in opened spot + return; + } + else if (data.size() == data.capacity()) // If we haven't hit the max but hit capacity + { + data.reserve(data.capacity() + pre_log_size); // Reserve more memory + } + data.push_back(std::move(t)); // In either case we have space under max length, move to new spot +} +Json::Value Log::Serialize() const +{ + Json::Value res; + for (uint32_t i = 0; i < data.size(); ++i) + { + res[i]["to"] = data[i].to; + res[i]["from"] = data[i].from; + res[i]["amount"] = (Json::UInt)data[i].amount; + res[i]["time"] = (Json::UInt64)data[i].time; + } + return res; +} diff --git a/src/transaction.cpp b/src/transaction.cpp new file mode 100644 index 0000000..e073c24 --- /dev/null +++ b/src/transaction.cpp @@ -0,0 +1,15 @@ +#include "transaction.h" + +Transaction::Transaction() = default; +Transaction::Transaction(std::string from_str, std::string to_str, uint32_t amount, uint64_t time) : amount(amount), time(time) +{ + from = std::move(from_str); + to = std::move(to_str); +} +Transaction::Transaction(std::string from_str, std::string to_str, uint32_t amount) : amount(amount) +{ + using namespace std::chrono; + from = std::move(from_str); + to = std::move(to_str); + time = duration_cast(system_clock::now().time_since_epoch()).count(); +} \ No newline at end of file diff --git a/src/user.cpp b/src/user.cpp new file mode 100644 index 0000000..1dfce5d --- /dev/null +++ b/src/user.cpp @@ -0,0 +1,51 @@ +#include "user.h" + +/** + * @brief User constructor + * + * @param init_pass initial password + */ +User::User(std::string &&init_pass) : password(XXH3_64bits(init_pass.data(), init_pass.size())) {} + +/** + * @brief User Constructor for admins + * + * @param init_bal initial balance + * @param init_pass initial password + */ +User::User(uint32_t init_bal, std::string &&init_pass) : balance(init_bal), password(XXH3_64bits(init_pass.data(), init_pass.size())) {} + +/** + * @brief User Constructor for loading + * + * @param init_bal + * @param init_pass + */ +User::User(uint32_t init_bal, uint64_t init_pass) : balance(init_bal), password(init_pass) {} +User::User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j) : balance(init_bal), password(init_pass) +{ + if (log_j.size()) + { + log.data.reserve(std::min(pre_log_size * ((log_j.size() / pre_log_size) + 1), max_log_size)); + for (uint32_t i = (log_j.size() - max_log_size) * (log_j.size() > max_log_size); i < log_j.size(); i++) + { + log.data.push_back(std::move(Transaction( + log_j[i]["from"].asCString(), + log_j[i]["to"].asCString(), + log_j[i]["amount"].asUInt(), + log_j[i]["time"].asUInt64()))); + } + } +} + +Json::Value User::Serialize() const +{ + Json::Value res; + res["balance"] = (Json::UInt)balance; + res["password"] = (Json::UInt64)password; + if constexpr (max_log_size) + { + res["log"] = log.Serialize(); + } + return res; +} \ No newline at end of file diff --git a/include/xxhash.c b/src/xxhash.c similarity index 100% rename from include/xxhash.c rename to src/xxhash.c