mirror of
https://github.com/Expand-sys/CCash
synced 2025-12-16 00:02:14 +11:00
✨ Split .hpp into .h and .cpp
This commit is contained in:
parent
e32dbbae03
commit
84f934f39d
19 changed files with 692 additions and 615 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
61
include/bank.h
Normal file
61
include/bank.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <shared_mutex>
|
||||
#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<std::string>,
|
||||
phmap::priv::hash_default_eq<std::string>,
|
||||
phmap::priv::Allocator<phmap::priv::Pair<const std::string, User>>,
|
||||
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
|
||||
329
include/bank.hpp
329
include/bank.hpp
|
|
@ -1,329 +0,0 @@
|
|||
#pragma once
|
||||
#include <fstream>
|
||||
#include <shared_mutex>
|
||||
#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<std::string>,
|
||||
phmap::priv::hash_default_eq<std::string>,
|
||||
phmap::priv::Allocator<phmap::priv::Pair<const std::string, User>>,
|
||||
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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex, std::shared_mutex> 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<Json::StreamWriter> 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
|
||||
55
include/bank_f.h
Normal file
55
include/bank_f.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
#include <drogon/HttpController.h>
|
||||
#include "bank.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
#define req_args const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback
|
||||
|
||||
class BankF : public HttpController<BankF, false>
|
||||
{
|
||||
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
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
13
include/log.h
Normal file
13
include/log.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <json/json.h>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include "consts.hpp"
|
||||
#include "transaction.h"
|
||||
|
||||
struct Log
|
||||
{
|
||||
std::vector<Transaction> data;
|
||||
void AddTrans(Transaction &&t);
|
||||
Json::Value Serialize() const;
|
||||
};
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include "consts.hpp"
|
||||
#include "transactions.hpp"
|
||||
|
||||
struct Log
|
||||
{
|
||||
std::vector<Transaction> 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;
|
||||
}
|
||||
};
|
||||
15
include/transaction.h
Normal file
15
include/transaction.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
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);
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#pragma once
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
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<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
};
|
||||
20
include/user.h
Normal file
20
include/user.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#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;
|
||||
};
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
#pragma once
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#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;
|
||||
}
|
||||
};
|
||||
6
main.cpp
6
main.cpp
|
|
@ -3,7 +3,7 @@
|
|||
#include <thread>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "bank_f.hpp"
|
||||
#include "bank_f.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -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<BankF>();
|
||||
auto API = std::make_shared<BankF>(&bank);
|
||||
app().registerPostHandlingAdvice(
|
||||
[](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) {
|
||||
resp->addHeader("Access-Control-Allow-Origin", "*");
|
||||
|
|
|
|||
295
src/bank.cpp
Normal file
295
src/bank.cpp
Normal file
|
|
@ -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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex, std::shared_mutex> 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<Json::StreamWriter> 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
|
||||
122
src/bank_f.cpp
Normal file
122
src/bank_f.cpp
Normal file
File diff suppressed because one or more lines are too long
31
src/log.cpp
Normal file
31
src/log.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
15
src/transaction.cpp
Normal file
15
src/transaction.cpp
Normal file
|
|
@ -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<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
51
src/user.cpp
Normal file
51
src/user.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in a new issue