Merge branch 'EntireTwix:Refractor' into Refractor

This commit is contained in:
Expand-sys 2021-07-13 08:00:06 +10:00 committed by GitHub
commit 9318314d0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 42074 additions and 493 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.vscode
build
ccash_config.hpp

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "drogon"]
path = third_party/drogon
url = https://github.com/an-tao/drogon
[submodule "third_party/xxHash"]
path = third_party/xxHash
url = https://github.com/Cyan4973/xxHash
[submodule "third_party/base64"]
path = third_party/base64
url = https://github.com/aklomp/base64

View file

@ -14,19 +14,20 @@ find_package(Threads REQUIRED)
add_executable(${PROJECT_NAME} main.cpp )
add_subdirectory(third_party/xxHash/cmake_unofficial third_party/xxHash/build EXCLUDE_FROM_ALL)
target_sources(${PROJECT_NAME} PRIVATE
src/json_filter.cpp
src/admin_filter.cpp
src/bank_api.cpp
src/bank_resp.cpp
src/bank.cpp
src/change_flag.cpp
src/json_filter.cpp
src/log.cpp
src/simdjson.cpp
src/str_intrusion.cpp
src/transaction.cpp
src/user_filter.cpp
src/user.cpp
src/xxhash_str.cpp
src/xxhash.c
)
if(DEFINED USER_SAVE_LOC)
@ -47,12 +48,6 @@ else()
set(MAX_LOG_SIZE_VAL 100)
endif()
if(DEFINED PRE_LOG_SIZE)
set(PRE_LOG_SIZE_VAL ${PRE_LOG_SIZE})
else()
set(PRE_LOG_SIZE_VAL 10)
endif()
if(DEFINED CONSERVATIVE_DISK_SAVE)
set(CONSERVATIVE_DISK_SAVE_VAL ${CONSERVATIVE_DISK_SAVE})
else()
@ -83,7 +78,6 @@ target_include_directories(${PROJECT_NAME} PUBLIC third_party/base64/include)
add_subdirectory(third_party/drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_THREAD_LIBS_INIT} )
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
#AVX2_CFLAGS=-mavx2 SSSE3_CFLAGS=-mssse3 SSE41_CFLAGS=-msse4.1 SSE42_CFLAGS=-msse4.2 AVX_CFLAGS=-mavx make lib/libbase64.o
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/base64/lib/libbase64.o)

View file

@ -1,14 +1,14 @@
* [problem/solution](docs/idea.md)
* [contributors](docs/contributors.md)
* connected services
* how to contribute
* [explanation](docs/connected_services/how_to/explanation.md)
* [language APIs](docs/connected_services/how_to/APIs.md)
* [API endpoints](docs/connected_services/how_to/endpoints.md)
* [language APIs](docs/connected_services/how_to/APIs.md)
* [API endpoints](docs/connected_services/how_to/endpoints.md)
* [existing services](docs/connected_services/existing_services.md)
* features
* [user side](docs/features/user_side.md)
* [implementation](docs/features/implementation.md)
* [building](docs/building.md)
* [FAQ](docs/FAQ.md)
* [contributors](docs/contributors.md)
* [Patreon](https://www.patreon.com/twoxx) if you wanna support this and/or future projects.

View file

@ -4,6 +4,7 @@
#include <sys/types.h>
#include <unistd.h>
#include <random>
#include "xxhash_str.h"
#include "bank.h"
#include <signal.h>
@ -62,12 +63,17 @@ static Bank bank;
int main(int argc, char **argv)
{
bank.AddUser("twix", 0, "root");
bank.AddUser("jolly", 0, "root");
bank.admin_account = "twix";
const std::string data("this string is quite long which is relevant when testing the speed of a hasing function");
Op(std::hash<std::string>{}(data), "hash<string>: ", 1000000);
Op(xxHashStringGen{}(data), "xxHashStringGen: ", 1000000);
Op_a(bank.AddUser("", 0, ""), "add user: ", 1000000, bank.DelUser(""));
Op(bank.AddBal("twix", 1), "give bal: ", 1000000);
Op(bank.SubBal("twix", 1), "give bal: ", 1000000);
Op(bank.ImpactBal("twix", 1), "impact bal: ", 1000000);
Op(bank.SetBal("twix", 1000000), "set bal: ", 1000000);
Op(bank.SendFunds("twix", "jolly", 1), "send funds: ", 1000000);
Op(bank.SendFunds("twix", "twix", 1), "invalid send funds: ", 1000000);
@ -81,11 +87,13 @@ int main(int argc, char **argv)
Op(bank.GetBal("twix"), "get bal: ", 1000000);
Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000);
Op(bank.ChangePassword("twix", "root"), "change pass: ", 1000000);
Op(bank.GetLogs("twix"), "get logs: ", 10000);
Op(bank.Save(), "saving: ", 1);
#if CONSERVATIVE_DISK_SAVE
Op(bank.GetChangeState(), "change flag: ", 10000);
#if MAX_LOG_SIZE > 0
Op(bank.GetLogs("twix"), "get logs: ", 1000000);
#endif
#if CONSERVATIVE_DISK_SAVE
Op(bank.GetChangeState(), "change flag: ", 1000000);
#endif
Op(bank.Save(), "saving: ", 1);
//GetBal scalining test
// std::default_random_engine generator;
@ -93,13 +101,13 @@ int main(int argc, char **argv)
// for (size_t i = 0; i < 10000000; ++i)
// {
// bank.AddUser(std::to_string(i), "root");
// bank.AddUser(std::to_string(i), 100000, "root");
// if (i % 10000 == 0)
// {
// auto u = std::to_string(distribution(generator) * i);
// auto u = std::to_string((int)(distribution(generator) * i));
// Op(bank.GetBal(u), std::to_string(i) + ", ", 100000);
// }
// }
return 0;
}
}

View file

@ -1,8 +1,7 @@
#pragma once
// Setting both values to 0 does not compile logging (useful for if disk/memory is very valuable)
// Setting to 0 does not compile logging (useful for if disk/memory is very valuable)
#define MAX_LOG_SIZE @MAX_LOG_SIZE_VAL@
#define PRE_LOG_SIZE @PRE_LOG_SIZE_VAL@
//default to minecraft usernames
constexpr unsigned min_name_size = 3;

View file

@ -0,0 +1 @@
[PREVIOUS PAGE](building.md)

View file

@ -1,14 +1,43 @@
# Building
[PREVIOUS PAGE](features/implementation.md) | [NEXT PAGE](FAQ.md)
## System Requirements
as CCash is very lightweight it can run on practically any device but here are some tips:
* single core machines should toggle `MULTI_THREADED` to `false`
* if your server is sufficiently active as so that each save frequency saving is highly likely then `CONSERVATIVE_DISK_SAVE` should be toggled to `false`
* `MAX_LOG_SIZE` should be adjusted as it takes up the most memory usage/storage of the ledger's features at ~202 bytes in memory and (size) in disk at default settings. Setting to 0 will not even compile logs
* with no users memory usage is 8.628477 Mb
## Drogon Depedencies
### Linux
#### Debian
`sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev`
```
sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev
```
#### CentOS 7.5
`yum install git gcc gcc-c++ && git clone https://github.com/Kitware/CMake && cd CMake/ && ./bootstrap && make && make install && yum install centos-release-scl && devtoolset-8 && scl enable devtoolset-8 bash && git clone https://github.com/open-source-parsers/jsoncpp && cd jsoncpp/ && mkdir build && cd build && cmake .. && make && make install && yum install libuuid-devel && yum install openssl-devel && yum install zlib-devel`
```
yum install git gcc gcc-c++
git clone https://github.com/Kitware/CMake
cd CMake/
./bootstrap
make
make install
yum install centos-release-scl devtoolset-8
scl enable devtoolset-8 bash
git clone https://github.com/open-source-parsers/jsoncpp
cd jsoncpp/
mkdir build
cd build
cmake ..
make
make install
yum install libuuid-devel openssl-devel zlib-devel
```
### MacOS
`brew install jsoncpp ossp-uuid openssl zlib`
```
brew install jsoncpp ossp-uuid openssl zlib
```
### Docker
Docker Package can be found [here](https://github.com/EntireTwix/CCash/packages/851105)
@ -20,22 +49,21 @@ git clone --recurse-submodule https://github.com/EntireTwix/CCash/
cd CCash
cd third_party/base64
AVX2_CFLAGS=-mavx2 SSSE3_CFLAGS=-mssse3 SSE41_CFLAGS=-msse4.1 SSE42_CFLAGS=-msse4.2 AVX_CFLAGS=-mavx make lib/libbase64.o
cd ..
cd ../..
mkdir build
cd build
```
### CMake Flags
there are multiple flags responsible configuring CCash:
| name | default | description | pros | cons |
| :--------------------- | :--------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------- |
| USER_SAVE_LOC | "../users.json" | where the users are saved | `N/A` | `N/A` |
| DROGON_CONFIG_LOC | "../config.json" | where the config is located | `N/A` | `N/A` |
| MAX_LOG_SIZE | 100 | max number of logs per user, last `n` transactions. If both this and pre log are toggled to 0 logs will not be compiled. | large history | higher memory usage |
| PRE_LOG_SIZE | 10 | the amount of transactions allocated in advance | faster to not alloc each transaction | higher memory usage |
| CONSERVATIVE_DISK_SAVE | `true` | when `true` only saves when changes are made | low # of disk operations | some atomic overhead |
| MULTI_THREADED | `true` | when `true` the program is compiled to utilize `n` threads which corresponds to how many Cores your CPU has, plus 1 for saving | speed | memory lock overhead may be in vain on single core machines |
| RETURN_ON_DEL_NAME | `N/A` | when defined, return on delete will be toggled and any accounts deleted will send their funds to the defined account, this prevent currency destruction | prevents destruction of currency | deleting accounts is made slower |
| name | default | description | pros | cons |
| :--------------------- | :--------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------- |
| USER_SAVE_LOC | "../users.json" | where the users are saved | `N/A` | `N/A` |
| DROGON_CONFIG_LOC | "../config.json" | where the config is located | `N/A` | `N/A` |
| MAX_LOG_SIZE | 100 | max number of logs per user, last `n` transactions. If both this and pre log are toggled to 0 logs will not be compiled. | large history | higher memory usage |
| CONSERVATIVE_DISK_SAVE | `true` | when `true` only saves when changes are made | low # of disk operations | some atomic overhead |
| MULTI_THREADED | `true` | when `true` the program is compiled to utilize `n` threads which corresponds to how many Cores your CPU has, plus 1 for saving | speed | memory lock overhead may be in vain on single core machines |
| RETURN_ON_DEL_NAME | `N/A` | when defined, return on delete will be toggled and any accounts deleted will send their funds to the defined account, this prevent currency destruction | prevents destruction of currency | deleting accounts is made slower |
simply running
@ -48,8 +76,12 @@ cmake -DMULTI_THREADING=false ..
```
with `-D`
### Lastly
### Finishing building
lastly type in
```
cmake <flags of your choice or none> ..
make -j<threads>
```
```
## Certs
make sure to edit `config.json` adding the certificate location if you're using HTTPS, I personally use [certbot](https://certbot.eff.org/), **it is Highly recommened you secure your server**.

View file

@ -0,0 +1 @@
[PREVIOUS PAGE](how_to/endpoints.md) | [NEXT PAGE](../features/user_side.md)

View file

@ -1,2 +1,8 @@
| language |
| -------- |
[PREVIOUS PAGE](explanation.md) | [NEXT PAGE](endpoints.md)
| language | v1 |
| ---------------------------------------------------------------------------------------- | :----------------------: |
| [Computer Craft](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua) | :heavy_multiplication_x: |
| [JS](https://github.com/LukeeeeBennett/ccash-client-js) | :heavy_multiplication_x: |
| [Python](https://github.com/FearlessDoggo21/CCashPythonClient) | :heavy_multiplication_x: |
| [Rust](https://git.stboyden.com/STBoyden/ccash-rs) | :heavy_multiplication_x: |

View file

@ -0,0 +1,41 @@
# API endpoints
[PREVIOUS PAGE](APIs.md) | [NEXT PAGE](../existing_services.md)
## KEY
`Jresp` - Json Response, json must be accepted in the `Accept` header, be that via `application/json` or `*/*`, failing to do so results in error `406`
`Jreq` - Json Request, requires `application/json` as `content-type`, failing to do so results in error `406`
`U` - User, requires [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) in the header `Authorization`. This credential must be a valid user, failing to do so results in error `401`
`A` - Admin, same as `U` but in addition requires username supplied be equal to the admin account username
:heavy_check_mark:
:heavy_multiplication_x:
### Usage endpoints
| name | purpose | json input | path | HTTP Method | correct status | return type | return value | Jresp | Jreq | A | U |
| :------------- | ------------------------------------------------------------------------------ | -------------------------------- | ------------------------------- | :---------: | :------------: | :--------------: | :--------------------------------------------: | :----------------: | :----------------------: | :----------------------: | :----------------------: |
| GetBal | retrieving the balance of a given user, `{name}` | `N/A` | api/v1/user/balance?name={name} | `GET` | 200 | uint32 | the user's balance | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: |
| GetLog | retrieves the logs of a given user, length varies by server configuration | `N/A` | api/v1/user/log | `GET` | 200 | array of objects | [{"to":string, "amount":uint32, "time":int64}] | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_check_mark: |
| SendFunds | sends funds from the authenticated user to the user `{name}` given in the json | {"name":string, "amount":uint32} | api/v1/user/transfer | `POST` | 200 | uint32 | the user's balance after the transaction | :heavy_check_mark: | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_check_mark: |
| VerifyPassword | verifies the credentials, used for connected services for ease of use | `N/A` | api/v1/user/verify_password | `POST` | 204 | `N/A` | `N/A` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_check_mark: |
### Usage enpoints errors
| name | 400 | 401 | 404 | 405 | 406 |
| :------------- | :----------------------: | :----------------------: | :----------------------: | :----------------: | :----------------: |
| GetBal | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| GetLog | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| SendFunds | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| VerifyPassword | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: |
all error responses have JSON string along with them to describe
### Usage endpoints support
`v` denoting the API version
| name | v1 |
| :------------- | :----------------: |
| GetBal | :heavy_check_mark: |
| GetLog | :heavy_check_mark: |
| SendFunds | :heavy_check_mark: |
| VerifyPassword | :heavy_check_mark: |

View file

@ -1,3 +1,5 @@
[PREVIOUS PAGE](../../idea.md) | [NEXT PAGE](APIs.md)
Using the ledger's API allows (you/others) to (make/use) connected services that utilize the ledger, below is a visual represenation of connected services:
![image](connected_a.png)

View file

@ -1,7 +1,8 @@
# Contributors
| name | work |
| :------------------------------------------ | ----------------------- |
| Caesay | Restful API suggestions |
| [Luke](https://github.com/LukeeeeBennett) | Docker package |
| [React](https://github.com/Reactified) | logo |
| [Doggo](https://github.com/FearlessDoggo21) | Logs optimized |
| name | work |
| :------------------------------------------ | -------------------------- |
| Caesay | Restful API suggestions |
| [Luke](https://github.com/LukeeeeBennett) | Docker package |
| [React](https://github.com/Reactified) | logo |
| [Expand](https://github.com/Expand-sys) | fixed docker package |
| [Doggo](https://github.com/FearlessDoggo21) | Logs optimized, Python API |

BIN
docs/features/GetBal().png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,18 @@
[PREVIOUS PAGE](user_side.md) | [NEXT PAGE](../building.md)
# Implementation Features
## Parallel Hashmap
<!-- memory vs database -->
<!-- phmap vs std hash map -->
<!-- https://greg7mdp.github.io/parallel-hashmap/ -->
### Scalability
below is `GetBal()` being called where `x` axis grows with # of users reaching 10 million users, `y` axis is time in ns. The name given is random between 0 and max users at that time as to provide more accurate results
![image](GetBal().png)
as the graph demonstrates, regardless of size GetBal remains consistent at around 39ns on my 3700x.
## xxHash
## base64
## simdjson
## drogon webframework
## multi-threading support
## intelligent saving
## Backwards Compatible API

View file

@ -0,0 +1,7 @@
[PREVIOUS PAGE](../connected_services/existing_services.md) | [NEXT PAGE](implementation.md)
# Features
## Performance
## Accessibility
## Security
## Other

View file

@ -1,3 +1,5 @@
[PREVIOUS PAGE](../README.md) | [NEXT PAGE](connected_services/how_to/explanation.md)
CCash is a web server hosting a ledger for Minecraft, able to be used from anything that can interact with its Restful API, including ComputerCraft.
the currency model most Minecraft Servers adopt if any, is resource based, usually diamonds, this model is fraught with issues however:
@ -20,4 +22,4 @@ or on localhost:
running it local to the minecraft server reduces latency for ComputerCraft connected services, fortunately CCash is sufficiently lightweight as to not impact performance on most setups.
**DISCLAIMER: if you are to run it locally and want to use ComputerCraft with it, make sure to add `127.0.0.1` to ComputerCraft's config section `allowed_domains`**
**DISCLAIMER: if you are to run it locally and want to use ComputerCraft with it, make sure to add `127.0.0.1` to ComputerCraft's config section `allowed_domains`**

View file

@ -1,19 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
#include <libbase64.h>
#include "bank.h"
using namespace drogon;
class AdminFilter : public HttpFilter<AdminFilter, false>
{
private:
Bank &bank;
public:
AdminFilter(Bank &);
virtual void doFilter(const HttpRequestPtr &,
FilterCallback &&,
FilterChainCallback &&) override;
};

View file

@ -2,15 +2,15 @@
#include <iostream> //temporary
#include <fstream>
#include <shared_mutex>
#include <drogon/HttpTypes.h>
#include <parallel-hashmap/parallel_hashmap/phmap.h>
#include "bank_resp.h"
#include "user.h"
#if (CONSERVATIVE_DISK_SAVE && MAX_LOG_SIZE < 0) && !MULTI_THREADED
#include "change_flag.h"
#endif
using BankResponse = std::pair<drogon::HttpStatusCode, Json::Value>;
bool ValidUsername(const std::string &name) noexcept;
class Bank
{
@ -30,7 +30,7 @@ class Bank
private:
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
ChangeFlag save_flag;
ChangeFlag<false> save_flag;
#else
bool save_flag = false;
#endif
@ -42,27 +42,29 @@ public:
std::string admin_account;
size_t NumOfUsers() const noexcept;
uint64_t NumOfLogs() const noexcept;
size_t NumOfLogs() const noexcept;
size_t SumBal() const noexcept;
#if CONSERVATIVE_DISK_SAVE
bool GetChangeState() const noexcept;
#endif
BankResponse GetBal(const std::string &name) const noexcept;
#if MAX_LOG_SIZE > 0
BankResponse GetLogs(const std::string &name) noexcept;
#endif
BankResponse SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount) noexcept;
bool VerifyPassword(std::string_view name, std::string_view attempt) const noexcept;
bool VerifyPassword(const std::string &name, const std::string_view &attempt) const noexcept;
void ChangePassword(const std::string &name, std::string &&new_pass) noexcept;
void ChangePassword(const std::string &name, const std::string &new_pass) noexcept;
BankResponse SetBal(const std::string &name, uint32_t amount) noexcept;
BankResponse AddBal(const std::string &name, uint32_t amount) noexcept;
BankResponse SubBal(const std::string &name, uint32_t amount) noexcept;
BankResponse ImpactBal(const std::string &name, int64_t amount) noexcept;
bool Contains(const std::string &name) const noexcept;
bool AdminVerifyAccount(std::string_view name) noexcept;
bool AdminVerifyAccount(const std::string &name) noexcept;
BankResponse AddUser(std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept;
BankResponse AddUser(const std::string &name, uint32_t init_bal, std::string &&init_pass) noexcept;
BankResponse DelUser(const std::string &name) noexcept;
void Save();
const char *Save();
void Load();
};

View file

@ -1,7 +1,7 @@
#pragma once
#include <drogon/HttpController.h>
#include "str_intrusion.h"
#include "json_filter.h"
#include "admin_filter.h"
#include "user_filter.h"
using namespace drogon;
@ -14,65 +14,65 @@ class api : public HttpController<api, false>
public:
api(Bank &b) noexcept;
void JsonCpp(req_args) const;
void Json(req_args) const;
#if API_VERSION >= 1
void GetBal(req_args, const std::string &name) const;
void GetLog(req_args);
void GetLogs(req_args);
void SendFunds(req_args) const;
void VerifyPassword(req_args) const;
void ChangePassword(req_args) const;
void AdminChangePassword(req_args) const;
void SetBal(req_args) const;
void AddBal(req_args) const;
void SubBal(req_args) const;
void ImpactBal(req_args) const;
void Help(req_args) const;
void Ping(req_args) const;
void Close(req_args) const;
void Contains(req_args, const std::string &name) const;
void AdminVerifyAccount(req_args) const;
void ApiVersion(req_args) const;
void ApiProperties(req_args) const;
void AddUser(req_args) const;
void AdminAddUser(req_args) const;
void DelUser(req_args) const;
void AdminDelUser(req_args) const;
#endif
METHOD_LIST_BEGIN
#if API_VERSION >= 1
//Usage
METHOD_ADD(api::GetBal, "/v1/user/balance?name={name}", Get, Options);
METHOD_ADD(api::GetBal, "/v1/user/balance?name={name}", Get, Options, "JsonFilter<false>");
#if MAX_LOG_SIZE > 0
METHOD_ADD(api::GetLog, "/v1/user/log", Get, Options, "UserFilter");
METHOD_ADD(api::GetLogs, "/v1/user/log", Get, Options, "JsonFilter<false>", "UserFilter<true, false>");
#else
METHOD_ADD(api::GetLog, "/v1/user/log", Get, Options);
METHOD_ADD(api::GetLogs, "/v1/user/log", Get, Options, "JsonFilter<false>");
#endif
METHOD_ADD(api::SendFunds, "/v1/user/transfer", Post, Options, "JsonFilter", "UserFilter"); //expects ["to"](string) and ["amount"](32 bits)
METHOD_ADD(api::VerifyPassword, "/v1/user/verify_password", Post, Options, "UserFilter");
METHOD_ADD(api::SendFunds, "/v1/user/transfer", Post, Options, "JsonFilter<true>", "UserFilter<true, false>"); //expects ["name"](string) and ["amount"](32 bits)
METHOD_ADD(api::VerifyPassword, "/v1/user/verify_password", Post, Options, "UserFilter<false, false>", "JsonFilter<false>");
//Meta Usage
METHOD_ADD(api::ChangePassword, "/v1/user/change_password", Patch, Options, "JsonFilter", "UserFilter"); //expects ["new_pass"](string)
METHOD_ADD(api::AdminChangePassword, "/v1/user/change_password", Patch, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string) and ["new_pass"](string)
METHOD_ADD(api::SetBal, "/v1/admin/set_balance", Patch, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string) and ["amount"](32 bits)
METHOD_ADD(api::AddBal, "/v1/admin/add_balance", Post, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string) and ["amount"](32 bits)
METHOD_ADD(api::SubBal, "/v1/admin/sub_balance", Post, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string) and ["amount"](32 bits)
METHOD_ADD(api::ChangePassword, "/v1/user/change_password", Patch, Options, "JsonFilter<true>", "UserFilter<true, false>"); //expects ["pass"](string)
METHOD_ADD(api::AdminChangePassword, "/v1/admin/user/change_password", Patch, Options, "JsonFilter<true>", "UserFilter<false, true>"); //expects ["name"](string) and ["pass"](string)
METHOD_ADD(api::SetBal, "/v1/admin/set_balance", Patch, Options, "JsonFilter<true>", "UserFilter<false, true>"); //expects ["name"](string) and ["amount"](32 bits)
METHOD_ADD(api::ImpactBal, "/v1/admin/impact_balance", Post, Options, "JsonFilter<true>", "UserFilter<false, true>"); //expects ["name"](string) and ["amount"](32 bits)
//System Usage
METHOD_ADD(api::Help, "/v1/help", Get, Options);
METHOD_ADD(api::Ping, "/v1/ping", Get, Options);
METHOD_ADD(api::Close, "/v1/admin/shutdown", Post, Options, "AdminFilter");
METHOD_ADD(api::Contains, "/v1/user/exists?name={name}", Get, Options);
METHOD_ADD(api::AdminVerifyAccount, "/v1/admin/verify_account", Post, Options, "AdminFilter");
METHOD_ADD(api::Close, "/v1/admin/shutdown", Post, Options, "UserFilter<false, true>", "JsonFilter<false>");
METHOD_ADD(api::Contains, "/v1/user/exists?name={name}", Get, Options, "JsonFilter<false>");
METHOD_ADD(api::AdminVerifyAccount, "/v1/admin/verify_account", Post, Options, "UserFilter<false, true>", "JsonFilter<false>");
//User Managment
METHOD_ADD(api::AddUser, "/v1/user/register", Post, Options, "JsonFilter"); //expects ["name"](string) ["pass"](string)
METHOD_ADD(api::AdminAddUser, "/v1/admin/user/register", Post, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string) ["balance"](32 bits) ["pass"](string)
METHOD_ADD(api::DelUser, "/v1/delete", Delete, Options, "UserFilter");
METHOD_ADD(api::AdminDelUser, "/v1/admin/delete", Delete, Options, "JsonFilter", "AdminFilter"); //expects ["name"](string)
METHOD_ADD(api::AddUser, "/v1/user/register", Post, Options); //expects ["name"](string) ["pass"](string)
METHOD_ADD(api::AdminAddUser, "/v1/admin/user/register", Post, Options, "JsonFilter<true>", "UserFilter<false, true>"); //expects ["name"](string) ["amount"](32 bits) ["pass"](string)
METHOD_ADD(api::DelUser, "/v1/user/delete", Delete, Options, "UserFilter<true, false>", "JsonFilter<false>");
METHOD_ADD(api::AdminDelUser, "/v1/admin/user/delete", Delete, Options, "JsonFilter<true>", "UserFilter<false, true>"); //expects ["name"](string)
#endif
METHOD_ADD(api::ApiVersion, "/version", Get, Options);
METHOD_ADD(api::ApiProperties, "/properties", Get, Options);
METHOD_LIST_END
};

12
include/bank_resp.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <optional>
#include <drogon/HttpTypes.h>
#include <drogon/HttpResponse.h>
#include <../src/HttpResponseImpl.h>
#include <../src/HttpAppFrameworkImpl.h>
using BankResponse = std::pair<drogon::HttpStatusCode, std::optional<std::string>>;
template <>
drogon::HttpResponsePtr drogon::toResponse(BankResponse &&data);

View file

@ -1,10 +1,11 @@
#pragma once
#include <atomic>
template <bool init>
class ChangeFlag
{
private:
std::atomic<bool> change_flag = false; //if true changes have been made
std::atomic<bool> change_flag = init; //if true changes have been made
public:
ChangeFlag() noexcept;

View file

@ -1,9 +1,11 @@
#pragma once
#include <drogon/HttpFilter.h>
#include "bank_resp.h"
using namespace drogon;
class JsonFilter : public HttpFilter<JsonFilter, false>
template <bool check_content_type>
class JsonFilter : public HttpFilter<JsonFilter<check_content_type>, false>
{
public:
JsonFilter();

View file

@ -5,12 +5,15 @@
#include "ccash_config.hpp"
#include "change_flag.h"
#include "transaction.h"
#include "simdjson.h"
using namespace simdjson;
struct Log
{
private:
ChangeFlag log_flag;
Json::Value log_snapshot;
ChangeFlag<true> log_flag;
std::string log_snapshot;
public:
#if MAX_LOG_SIZE == 1
@ -19,7 +22,7 @@ public:
std::vector<Transaction> data;
#endif
const Json::Value &GetLog() noexcept;
void AddTrans(Transaction &&t) noexcept;
const std::string &GetLogs() noexcept;
void AddTrans(const Transaction &t) noexcept;
Json::Value Serialize() const; // to be removed later
};

23863
include/simdjson.h Normal file

File diff suppressed because it is too large Load diff

10
include/str_intrusion.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include <string>
struct StrFromSV_Wrapper
{
std::string str;
StrFromSV_Wrapper() noexcept;
StrFromSV_Wrapper(std::string_view sv) noexcept;
~StrFromSV_Wrapper() noexcept;
};

View file

@ -9,7 +9,7 @@ struct Transaction
uint32_t amount = 0;
time_t time = 0;
Transaction();
Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount, time_t time);
Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount);
Transaction() noexcept;
Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount, time_t time) noexcept;
Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount) noexcept;
};

View file

@ -1,17 +1,19 @@
#pragma once
#include <drogon/HttpFilter.h>
#include <libbase64.h>
#include "str_intrusion.h"
#include "bank.h"
using namespace drogon;
class UserFilter : public HttpFilter<UserFilter, false>
template <bool set_body_flag, bool require_admin>
class UserFilter : public HttpFilter<UserFilter<set_body_flag, require_admin>, false>
{
private:
Bank &bank;
public:
UserFilter(Bank &);
UserFilter(Bank &b);
virtual void doFilter(const HttpRequestPtr &,
FilterCallback &&,

5325
include/xxhash.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,9 @@
#pragma once
#include <string>
#include <xxhash.h>
#include "xxhash.h"
struct xxHashStringGen
{
XXH64_hash_t operator()(const std::string &str) const noexcept;
XXH64_hash_t operator()(std::string &&str) const noexcept;
XXH64_hash_t operator()(std::string_view str) const noexcept;
XXH64_hash_t operator()(const std::string_view &str) const noexcept;
};

135
main.cpp
View file

@ -21,98 +21,91 @@ static Bank bank;
void SaveSig(int s)
{
std::cout << "\nSaving on close...\n";
bank.Save();
std::cout << "\nSaving on close...\n"
<< bank.Save();
exit(1);
}
int main(int argc, char **argv)
{
static_assert(bool(MAX_LOG_SIZE) == bool(PRE_LOG_SIZE), "You must either utilize both or neither logging variables.\n");
static_assert(MAX_LOG_SIZE >= PRE_LOG_SIZE, "The maximum log size must be larger than or equal to the amount preallocated.\n");
static_assert(!MAX_LOG_SIZE || !(MAX_LOG_SIZE % PRE_LOG_SIZE), "The maximum log size must be divisible by the preallocation size.\n");
if (argc != 3)
{
std::cerr << "Usage: sudo ./bank <admin account> <saving frequency in minutes>\n";
return 0;
}
if (geteuid() != 0)
{
std::cerr << "ERROR: CCash MUST be ran as root\n";
return 0;
}
std::cout
<< "\nAVX : " << (__builtin_cpu_supports("avx") ? "enabled" : "disabled")
<< "\nAVX 2 : " << (__builtin_cpu_supports("avx2") ? "enabled" : "disabled")
<< "\nSSE 2 : " << (__builtin_cpu_supports("sse2") ? "enabled" : "disabled")
<< "\nSSE 3 : " << (__builtin_cpu_supports("sse3") ? "enabled" : "disabled")
<< "\nSSE 4.1 : " << (__builtin_cpu_supports("sse4.1") ? "enabled" : "disabled")
<< "\nSSE 4.2 : " << (__builtin_cpu_supports("sse4.2") ? "enabled" : "disabled")
if (argc != 3)
{
std::cerr << "Usage: sudo ./bank <admin account> <saving frequency in minutes>\n";
return 0;
}
if (geteuid() != 0)
{
std::cerr << "ERROR: CCash MUST be ran as root\n";
return 0;
}
std::cout
<< "\nAPI Version : " << API_VERSION
<< "\n\nAVX : " << (__builtin_cpu_supports("avx") ? "enabled" : "disabled")
<< "\nAVX 2 : " << (__builtin_cpu_supports("avx2") ? "enabled" : "disabled")
<< "\nSSE 2 : " << (__builtin_cpu_supports("sse2") ? "enabled" : "disabled")
<< "\nSSE 3 : " << (__builtin_cpu_supports("sse3") ? "enabled" : "disabled")
<< "\nSSE 4.1 : " << (__builtin_cpu_supports("sse4.1") ? "enabled" : "disabled")
<< "\nSSE 4.2 : " << (__builtin_cpu_supports("sse4.2") ? "enabled" : "disabled")
#if MULTI_THREADED
<< "\n\nThreads : " << get_nprocs() + 1
<< "\nMulti threading : enabled";
<< "\n\nThreads : " << get_nprocs() + 1
<< "\nMulti threading : enabled";
#else
<< "\n\nThreads : " << 2
<< "\nMulti threading : disabled";
<< "\n\nThreads : " << 2
<< "\nMulti threading : disabled";
#endif
//Loading users from users.json
bank.Load();
//Loading users from users.json
bank.Load();
size_t num_of_logs = bank.NumOfLogs();
size_t num_of_users = bank.NumOfUsers();
std::cout << "\n\nLoaded " << num_of_users << " Users ~" << (float)(sizeof(User) * num_of_users) / 1048576 << "Mb"
<< "\nLoaded " << num_of_logs << " Logs ~" << (float)(num_of_logs * (90 + 80 + (max_name_size * 2))) / 1048576 << "Mb" //90:string representation(heap), sizeof(Transaction), max_name_size*2:filled to&from(heap)
<< "\nLoaded " << bank.SumBal() << " CSH"
<< std::endl; //flushing before EventLoop
std::cout << "\n\nLoaded " << bank.NumOfUsers() << " Users"
<< "\nLoaded " << bank.NumOfLogs() << " Logs"
<< std::endl; //flushing before EventLoop
//Sig handling
struct sigaction sigIntHandler;
//Sig handling
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = SaveSig;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigIntHandler.sa_handler = SaveSig;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
sigaction(SIGINT, &sigIntHandler, NULL);
//Admin account
bank.admin_account = argv[1];
//Admin account
bank.admin_account = argv[1];
//Auto Saving
const unsigned long saving_freq = std::stoul(std::string(argv[2]));
if (saving_freq) //if saving frequency is 0 then auto saving is turned off
{
std::thread([saving_freq]() {
while (1)
{
std::this_thread::sleep_for(std::chrono::minutes(saving_freq));
std::cout << "Saving " << std::time(0) << '\n';
if (bank.GetChangeState())
//Auto Saving
const unsigned long saving_freq = std::stoul(std::string(argv[2]));
if (saving_freq) //if saving frequency is 0 then auto saving is turned off
{
std::thread([saving_freq]() {
while (1)
{
std::cout << " to disk...\n";
bank.Save();
std::this_thread::sleep_for(std::chrono::minutes(saving_freq));
std::cout << "Saving " << std::time(0) << "...\n"
<< bank.Save();
}
else
{
std::cout << " no changes...\n";
}
}
})
.detach();
}
})
.detach();
}
} //destroying setup variables
static auto API = std::make_shared<api>(bank);
static auto user_filter_default = std::make_shared<UserFilter<true, false>>(bank);
static auto user_filter_sparse = std::make_shared<UserFilter<false, false>>(bank);
static auto admin_filter = std::make_shared<UserFilter<false, true>>(bank);
static auto json_resp_and_req_filter = std::make_shared<JsonFilter<true>>();
static auto json_resp_filter = std::make_shared<JsonFilter<false>>();
auto API = std::make_shared<api>(bank);
auto user_filter = std::make_shared<UserFilter>(bank);
auto admin_filter = std::make_shared<AdminFilter>(bank);
auto accept_filter = std::make_shared<JsonFilter>();
app().registerPostHandlingAdvice(
[](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) {
resp->addHeader("Access-Control-Allow-Origin", "*"); //CORS
});
app()
.loadConfigFile(config_location)
.registerFilter(user_filter)
.registerFilter(user_filter_default)
.registerFilter(user_filter_sparse)
.registerFilter(admin_filter)
.registerFilter(json_resp_and_req_filter)
.registerFilter(json_resp_filter)
.registerController(API)
#if MULTI_THREADED
.setThreadNum(get_nprocs())

View file

@ -1,39 +0,0 @@
#include "admin_filter.h"
AdminFilter::AdminFilter(Bank &b) : bank(b) {}
void AdminFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
std::string_view auth_header = req->getHeader("Authorization");
if (auth_header.size() > 6)
{
if (auth_header.substr(0, 6) == "Basic ")
{
std::string_view base64_input = auth_header.substr(6);
char base64_result[(base64_input.size() * 3) / 4];
size_t new_sz;
base64_decode(base64_input.data(), base64_input.size(), base64_result, &new_sz, 0);
std::string_view results_view(base64_result, new_sz);
std::size_t middle = results_view.find(':');
if (middle != std::string::npos)
{
std::string_view username = results_view.substr(0, middle);
if (bank.AdminVerifyAccount(username))
{
std::string_view password = results_view.substr(middle + 1);
if (bank.VerifyPassword(username, password))
{
fccb();
return;
}
}
}
}
}
const auto &resp = HttpResponse::newHttpJsonResponse("Invalid Credentials");
resp->setStatusCode(k401Unauthorized);
fcb(resp);
}

View file

@ -2,7 +2,7 @@
using namespace drogon;
__attribute__((always_inline)) inline bool ValidUsrname(const std::string &name) noexcept
bool ValidUsername(const std::string &name) noexcept
{
if (name.size() < min_name_size || name.size() > max_name_size)
{
@ -10,7 +10,7 @@ __attribute__((always_inline)) inline bool ValidUsrname(const std::string &name)
}
for (const char &c : name)
{
if (!(std::isalpha(c) || std::isdigit(c) || c == '_'))
if (!((c >= 97 && c <= 122) || std::isdigit(c) || c == '_'))
{
return false;
}
@ -22,12 +22,32 @@ __attribute__((always_inline)) inline bool ValidUsrname(const std::string &name)
size_t Bank::NumOfUsers() const noexcept { return users.size(); }
//NOT THREAD SAFE
uint64_t Bank::NumOfLogs() const noexcept
size_t Bank::NumOfLogs() const noexcept
{
uint64_t res = 0;
size_t res = 0;
#if MAX_LOG_SIZE > 0
for (const auto &u : users)
{
#if MAX_LOG_SIZE == 1
if (u.second.log.data.amount)
{
++res;
}
#else
res += u.second.log.data.size();
#endif
}
#endif
return res;
}
//NOT THREAD SAFE
size_t Bank::SumBal() const noexcept
{
size_t res = 0;
for (const auto &u : users)
{
res += u.second.balance;
}
return res;
}
@ -45,77 +65,83 @@ bool Bank::GetChangeState() const noexcept
BankResponse Bank::GetBal(const std::string &name) const noexcept
{
uint64_t res = 0;
users.if_contains(name, [&res](const User &u) { res = u.balance + 1; });
return res ? BankResponse(k200OK, res - 1) : BankResponse(k404NotFound, "User not found");
uint32_t res = 0;
if (!users.if_contains(name, [&res](const User &u) { res = u.balance; }))
{
return {k404NotFound, "\"User not found\""};
}
else
{
return {k200OK, std::to_string(res)};
}
}
#if MAX_LOG_SIZE > 0
BankResponse Bank::GetLogs(const std::string &name) noexcept
{
BankResponse res;
#if MAX_LOG_SIZE > 0
if (!users.modify_if(name, [&res](User &u) { res = {k200OK, u.log.GetLog()}; }))
if (!users.modify_if(name, [&res](User &u) { res = {k200OK, u.log.GetLogs()}; }))
{
return BankResponse(k404NotFound, "User not found");
return {k404NotFound, "\"User not found\""};
}
else
{
return res;
}
#endif
return res;
}
#endif
BankResponse Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount) noexcept
{
//cant send money to self, from self or amount is 0
if (a_name == b_name)
{
return {k400BadRequest, "Sender and Reciever names cannot match"};
return {k400BadRequest, "\"Sender and Reciever names cannot match\""};
}
//cant send 0
if (!amount)
{
return {k400BadRequest, "Amount being sent cannot be 0"};
return {k400BadRequest, "\"Amount being sent cannot be 0\""};
}
//as first modify_if checks a_name and grabs unique lock
if (!Contains(b_name))
{
return {k404NotFound, "Reciever does not exist"};
return {k404NotFound, "\"Reciever does not exist\""};
}
BankResponse state;
std::shared_lock<std::shared_mutex> lock{save_lock}; //about 10% of this function's cost
#if MAX_LOG_SIZE > 0
Transaction temp(a_name, b_name, amount);
if (!users.modify_if(a_name, [&temp, &state, amount](User &a) {
#else
if (!users.modify_if(a_name, [&state, amount](User &a) {
static thread_local Transaction temp(a_name, b_name, amount);
#endif
if (!users.modify_if(a_name, [&state, amount](User &a) {
//if A can afford it
if (a.balance < amount)
{
state = {k400BadRequest, "Sender has insufficient funds"};
state = {k400BadRequest, "\"Sender has insufficient funds\""};
}
else
{
a.balance -= amount;
#if MAX_LOG_SIZE > 0
a.log.AddTrans(Transaction(temp)); //about 40% of this function's cost
a.log.AddTrans(temp); //about 40% of this function's cost
#endif
state = {k200OK, "Transfer successful!"};
state = {k200OK, std::to_string(a.balance)};
}
}))
{
return {k404NotFound, "Sender does not exist"};
return {k404NotFound, "\"Sender does not exist\""};
}
if (state.first == k200OK)
{
#if MAX_LOG_SIZE > 0
users.modify_if(b_name, [&temp, amount](User &b) {
users.modify_if(b_name, [amount](User &b) {
b.balance += amount;
b.log.AddTrans(std::move(temp));
b.log.AddTrans(temp);
}); //about 40% of this function's cost
#else
users.modify_if(b_name, [amount](User &b) { b.balance += amount; });
#endif
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
save_flag.SetChangesOn(); //about 5% of this function's cost
@ -126,16 +152,15 @@ BankResponse Bank::SendFunds(const std::string &a_name, const std::string &b_nam
}
return state;
}
bool Bank::VerifyPassword(std::string_view name, std::string_view attempt) const noexcept
bool Bank::VerifyPassword(const std::string &name, const std::string_view &attempt) const noexcept
{
bool res = false;
users.if_contains(name.data(), [&res, &attempt](const User &u) { res = (u.password == xxHashStringGen{}(attempt)); });
users.if_contains(name, [&res, &attempt](const User &u) { res = (u.password == xxHashStringGen{}(attempt)); });
return res;
}
void Bank::ChangePassword(const std::string &name, std::string &&new_pass) noexcept
void Bank::ChangePassword(const std::string &name, const std::string &new_pass) noexcept
{
users.modify_if(name, [&new_pass](User &u) { u.password = xxHashStringGen{}(new_pass); });
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
save_flag.SetChangesOn();
@ -143,10 +168,15 @@ void Bank::ChangePassword(const std::string &name, std::string &&new_pass) noexc
save_flag = true;
#endif
#endif
users.modify_if(name, [&new_pass](User &u) { u.password = xxHashStringGen{}(new_pass); });
}
BankResponse Bank::SetBal(const std::string &name, uint32_t amount) noexcept
{
if (users.modify_if(name, [amount](User &u) { u.balance = amount; }))
if (!users.modify_if(name, [amount](User &u) { u.balance = amount; }))
{
return {k404NotFound, "\"User not found\""};
}
else
{
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
@ -155,20 +185,17 @@ BankResponse Bank::SetBal(const std::string &name, uint32_t amount) noexcept
save_flag = true;
#endif
#endif
return {k200OK, "Balance set!"};
}
else
{
return {k404NotFound, "User not found"};
return {k204NoContent, std::nullopt}; //may return new balance
}
}
BankResponse Bank::AddBal(const std::string &name, uint32_t amount) noexcept
BankResponse Bank::ImpactBal(const std::string &name, int64_t amount) noexcept
{
if (amount)
if (amount == 0)
{
return {k400BadRequest, "Amount cannot be 0"};
return {k400BadRequest, "\"Amount cannot be 0\""};
}
if (users.modify_if(name, [amount](User &u) { u.balance += amount; }))
uint32_t balance;
if (users.modify_if(name, [&balance, amount](User &u) { balance = (u.balance < (amount * -1) ? u.balance = 0 : u.balance += amount); }))
{
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
@ -177,49 +204,26 @@ BankResponse Bank::AddBal(const std::string &name, uint32_t amount) noexcept
save_flag = true;
#endif
#endif
return {k200OK, "Balance added!"};
return {k200OK, std::to_string(balance)}; //may return new balance
}
else
{
return {k404NotFound, "User not found"};
}
}
BankResponse Bank::SubBal(const std::string &name, uint32_t amount) noexcept
{
if (amount)
{
return {k400BadRequest, "Amount cannot be 0"};
}
if (users.modify_if(name, [amount](User &u) { amount > u.balance ? u.balance = 0 : u.balance -= amount; }))
{
#if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED
save_flag.SetChangesOn();
#else
save_flag = true;
#endif
#endif
return {k200OK, "Balance subtracted!"};
}
else
{
return {k404NotFound, "User not found"};
return {k404NotFound, "\"User not found\""};
}
}
bool Bank::Contains(const std::string &name) const noexcept
{
return users.contains(name);
}
bool Bank::AdminVerifyAccount(std::string_view name) noexcept
bool Bank::AdminVerifyAccount(const std::string &name) noexcept
{
return (name == admin_account);
}
BankResponse Bank::AddUser(std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept
BankResponse Bank::AddUser(const std::string &name, uint32_t init_bal, std::string &&init_pass) noexcept
{
if (!ValidUsrname(name))
if (!ValidUsername(name))
{
return {k400BadRequest, "Invalid Name, breaks size and/or character restrictions"};
return {k400BadRequest, "\"Invalid Name, breaks size and/or character restrictions\""};
}
std::shared_lock<std::shared_mutex> lock{save_lock};
if (users.try_emplace_l(
@ -232,11 +236,11 @@ BankResponse Bank::AddUser(std::string &&name, uint32_t init_bal, std::string &&
save_flag = true;
#endif
#endif
return {k200OK, "User added!"};
return {k204NoContent, std::nullopt};
}
else
{
return {k409Conflict, "User already exists"};
return {k409Conflict, "\"User already exists\""};
}
}
BankResponse Bank::DelUser(const std::string &name) noexcept
@ -244,11 +248,10 @@ BankResponse Bank::DelUser(const std::string &name) noexcept
std::shared_lock<std::shared_mutex> lock{save_lock};
#if RETURN_ON_DEL
uint32_t bal;
if (users.if_contains(name, [this, &bal](const User &u) {
bal = u.balance;
}))
if (users.if_contains(name, [&bal](const User &u) { bal = u.balance; }) &&
bal)
{
users.modify_if(return_account, [ this, bal ](User & u))
users.modify_if(return_account, [bal](User & u))
{
u.balance += bal;
}
@ -263,20 +266,20 @@ BankResponse Bank::DelUser(const std::string &name) noexcept
save_flag = true;
#endif
#endif
return BankResponse(k200OK, "User deleted!");
return {k204NoContent, std::nullopt};
}
else
{
return BankResponse(k404NotFound, "User not found");
return {k404NotFound, "\"User not found\""};
}
}
void Bank::Save()
const char *Bank::Save()
{
#if CONSERVATIVE_DISK_SAVE
if (GetChangeState())
{
#endif
Json::Value temp;
static thread_local Json::Value temp;
//loading info into json temp
{
@ -284,7 +287,7 @@ void Bank::Save()
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.data()] = u_val.Serialize(); });
users.if_contains(u.first, [&u](const User &u_val) { temp[u.first] = u_val.Serialize(); });
}
}
if (temp.isNull())
@ -293,9 +296,9 @@ void Bank::Save()
}
else
{
std::ofstream user_save(users_location);
Json::StreamWriterBuilder builder;
const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
static thread_local std::ofstream user_save(users_location);
static thread_local Json::StreamWriterBuilder builder;
static thread_local const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(temp, &user_save);
user_save.close();
}
@ -305,6 +308,11 @@ void Bank::Save()
#else
save_flag = true;
#endif
return " to disk...\n";
}
else
{
return " no changes...\n";
}
#endif
}
@ -312,12 +320,12 @@ void Bank::Save()
//NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS
void Bank::Load()
{
Json::CharReaderBuilder builder;
static thread_local Json::CharReaderBuilder builder;
Json::Value temp;
std::ifstream user_save(users_location);
static thread_local Json::Value temp;
static thread_local std::ifstream user_save(users_location);
builder["collectComments"] = true;
JSONCPP_STRING errs;
static thread_local JSONCPP_STRING errs;
if (!parseFromStream(builder, user_save, &temp, &errs))
{
std::cerr << errs << '\n';

View file

@ -1,54 +1,37 @@
#include "bank_api.h"
// const auto data(R);
// auto res = std::make_shared<HttpResponseImpl>(data.first, CT_APPLICATION_JSON);
// res->setJsonObject(JsonCast(std::move(data.second)));
// doResponseCreateAdvices(res);
// callback(res);
//all my homies hate jsoncpp
#define CACHE_FOREVER resp->setExpiredTime(0);
#define CACHE_FOREVER resp->setExpiredTime(0)
#define GEN_BODY \
const auto temp_req = req->getJsonObject(); \
const auto body = temp_req ? *temp_req : Json::Value();
#define CORS resp->addHeader("Access-Control-Allow-Origin", "*")
#define RESPONSE_PARSE(R) \
const auto r(R); \
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(std::move(r.second))); \
resp->setStatusCode(r.first); \
callback(resp);
#define GEN_BODY \
static thread_local const auto temp_req = req->getJsonObject(); \
static thread_local const auto body = temp_req ? *temp_req : Json::Value()
#define RESPOND_TRUE \
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(true)); \
CACHE_FOREVER \
callback(resp);
static thread_local ondemand::parser parser;
#define SIMD_JSON_GEN \
static thread_local simdjson::padded_string input(req->getBody()); \
static thread_local ondemand::document doc = parser.iterate(input)
#define NAME_PARAM req->getBody().data()
#define RESPONSE_PARSE(R) \
static thread_local auto resp = HttpResponse::newCustomHttpResponse(R); \
CORS; \
callback(resp)
template <typename T>
constexpr Json::Value JsonCast(T &&val)
#define RESPOND_TRUE \
static thread_local auto resp = HttpResponse::newCustomHttpResponse(BankResponse(k204NoContent, std::nullopt)); \
CORS; \
CACHE_FOREVER; \
callback(resp)
#define NAME_PARAM req->getParameter("name")
api::api(Bank &b) noexcept : bank(b)
{
if constexpr (std::is_same_v<T, int_fast8_t>)
{
return (int)val; //becuase of json lib interpreting 67 as 'A' for example
}
else if constexpr (std::is_same_v<T, uint64_t>)
{
return (Json::UInt64)val;
}
else if constexpr (std::is_same_v<T, uint32_t>)
{
return (Json::UInt)val;
}
else
{
return val;
}
}
api::api(Bank &b) noexcept : bank(b) {}
#if API_VERSION >= 1
//Usage
@ -56,7 +39,7 @@ void api::GetBal(req_args, const std::string &name) const
{
RESPONSE_PARSE(bank.GetBal(name));
}
void api::GetLog(req_args)
void api::GetLogs(req_args)
{
if constexpr (MAX_LOG_SIZE > 0)
{
@ -64,99 +47,196 @@ void api::GetLog(req_args)
}
else
{
auto resp = HttpResponse::newHttpJsonResponse("Logs are Disabled");
resp->setStatusCode(k404NotFound);
CACHE_FOREVER
static thread_local auto resp = HttpResponse::newCustomHttpResponse(BankResponse(k404NotFound, "\"Logs are Disabled\""));
CORS;
CACHE_FOREVER;
callback(resp);
}
}
void api::SendFunds(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.SendFunds(NAME_PARAM, body["to"].asCString(), body["amount"].asUInt()));
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto amount = doc.find_field("amount").get_uint64();
BankResponse res;
if (name.error() || amount.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
res = bank.SendFunds(NAME_PARAM, name_val.str, amount.value());
}
RESPONSE_PARSE(std::move(res));
}
void api::VerifyPassword(req_args) const { RESPOND_TRUE }
void api::VerifyPassword(req_args) const { RESPOND_TRUE; }
//Meta Usage
void api::ChangePassword(req_args) const
{
GEN_BODY
bank.ChangePassword(NAME_PARAM, std::move(body["new_pass"].asCString()));
RESPOND_TRUE
SIMD_JSON_GEN;
auto pass = doc.find_field("pass").get_string();
BankResponse res;
if (pass.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper pass_val(pass.value());
bank.ChangePassword(NAME_PARAM, std::move(pass_val.str));
}
RESPOND_TRUE;
}
void api::AdminChangePassword(req_args) const
{
GEN_BODY
bank.ChangePassword(body["name"].asCString(), std::move(body["new_pass"].asCString()));
RESPOND_TRUE
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto pass = doc.find_field("pass").get_string();
BankResponse res;
if (name.error() || pass.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
StrFromSV_Wrapper pass_val(pass.value());
bank.ChangePassword(name_val.str, std::move(pass_val.str));
}
RESPOND_TRUE;
}
void api::SetBal(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.SetBal(body["name"].asCString(), body["amount"].asUInt()));
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto amount = doc.find_field("amount").get_uint64();
BankResponse res;
if (name.error() || amount.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
res = bank.SetBal(name_val.str, amount.value());
}
RESPONSE_PARSE(std::move(res));
}
void api::AddBal(req_args) const
void api::ImpactBal(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.AddBal(body["name"].asCString(), body["amount"].asUInt()));
}
void api::SubBal(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.AddBal(body["name"].asCString(), body["amount"].asUInt()));
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto amount = doc.find_field("amount").get_int64();
BankResponse res;
if (name.error() || amount.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
res = bank.ImpactBal(name_val.str, amount.value());
}
RESPONSE_PARSE(std::move(res));
}
//System Usage
void api::Help(req_args) const
{
auto resp = HttpResponse::newRedirectionResponse("https://github.com/EntireTwix/CCash/blob/Refractor/README.md"); //may make README.md
static thread_local auto resp = HttpResponse::newRedirectionResponse("https://github.com/EntireTwix/CCash/blob/README.md");
CACHE_FOREVER;
callback(resp);
}
void api::Ping(req_args) const
{
RESPOND_TRUE
}
void api::Close(req_args) const
{
bank.Save();
RESPOND_TRUE; //filter handles admin creds
app().quit();
RESPOND_TRUE //filter handles admin creds
}
void api::Contains(req_args, const std::string &name) const
{
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(bank.Contains(name)));
callback(resp);
RESPONSE_PARSE(BankResponse(k200OK, bank.Contains(name) ? "true" : "false"));
}
void api::AdminVerifyAccount(req_args) const
{
RESPOND_TRUE //filter handles admin creds
RESPOND_TRUE; //filter handles admin creds
}
void api::ApiVersion(req_args) const
void api::ApiProperties(req_args) const
{
auto resp = HttpResponse::newHttpJsonResponse(API_VERSION);
//yet to be converted to simdjson
Json::Value temp;
temp["version"] = API_VERSION;
temp["max_log"] = MAX_LOG_SIZE;
temp["min_name"] = min_name_size;
temp["max_name"] = max_name_size;
temp["return_on_del"] = RETURN_ON_DEL;
if constexpr (RETURN_ON_DEL)
{
temp["return_on_del_acc"] = return_account;
}
static thread_local auto resp = HttpResponse::newHttpJsonResponse(std::move(temp));
CORS;
CACHE_FOREVER;
callback(resp);
}
void api::AddUser(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.AddUser(body["name"].asCString(), 0, body["pass"].asCString()))
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto pass = doc.find_field("pass").get_string();
BankResponse res;
if (name.error() || pass.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
StrFromSV_Wrapper pass_val(pass.value());
res = bank.AddUser(std::move(name_val.str), 0, std::move(pass_val.str));
}
RESPONSE_PARSE(std::move(res));
}
void api::AdminAddUser(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.AddUser(body["name"].asCString(), body["balance"].asUInt(), body["pass"].asCString()))
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
auto amount = doc.find_field("amount").get_uint64();
auto pass = doc.find_field("pass").get_string();
BankResponse res;
if (name.error() || amount.error() || pass.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
StrFromSV_Wrapper pass_val(pass.value());
res = bank.AddUser(std::move(name_val.str), amount.value(), std::move(pass_val.str));
}
RESPONSE_PARSE(std::move(res));
}
void api::DelUser(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.DelUser(NAME_PARAM))
RESPONSE_PARSE(bank.DelUser(NAME_PARAM));
}
void api::AdminDelUser(req_args) const
{
GEN_BODY
RESPONSE_PARSE(bank.DelUser(body["name"].asCString()))
SIMD_JSON_GEN;
auto name = doc.find_field("name").get_string();
BankResponse res;
if (name.error())
{
res = BankResponse(k400BadRequest, "Invalid JSON");
}
else
{
StrFromSV_Wrapper name_val(name.value());
res = bank.DelUser(name_val.str);
}
RESPONSE_PARSE(std::move(res));
}
#endif

26
src/bank_resp.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "bank_resp.h"
template <>
drogon::HttpResponsePtr drogon::toResponse(BankResponse &&data)
{
std::shared_ptr<HttpResponseImpl> res;
if (data.second)
{
res = std::make_shared<HttpResponseImpl>(data.first, CT_APPLICATION_JSON);
res->setBody(std::move(*data.second));
}
else
{
res = std::make_shared<HttpResponseImpl>();
res->setStatusCode(data.first);
}
const auto &advices = HttpAppFrameworkImpl::instance().getResponseCreationAdvices();
if (!advices.empty())
{
for (auto &advice : advices)
{
advice(res);
}
}
return res;
}

View file

@ -1,21 +1,28 @@
#include "change_flag.h"
ChangeFlag::ChangeFlag() noexcept {}
ChangeFlag::ChangeFlag(ChangeFlag &&f) noexcept
template <bool init>
ChangeFlag<init>::ChangeFlag() noexcept {}
template <bool init>
ChangeFlag<init>::ChangeFlag(ChangeFlag &&f) noexcept
{
change_flag.store(f.GetChangeState(), std::memory_order_release); //is this safe?
change_flag.store(f.GetChangeState(), std::memory_order_release);
}
void ChangeFlag::SetChangesOn() noexcept
template <bool init>
void ChangeFlag<init>::SetChangesOn() noexcept
{
return change_flag.store(1, std::memory_order_release);
}
void ChangeFlag::SetChangesOff() noexcept
template <bool init>
void ChangeFlag<init>::SetChangesOff() noexcept
{
return change_flag.store(0, std::memory_order_release);
}
bool ChangeFlag::GetChangeState() const noexcept
template <bool init>
bool ChangeFlag<init>::GetChangeState() const noexcept
{
return change_flag.load(std::memory_order_acquire);
}
}
template class ChangeFlag<true>;
template class ChangeFlag<false>;

View file

@ -1,23 +1,41 @@
#include "json_filter.h"
JsonFilter::JsonFilter() {}
template <bool check_content_type>
JsonFilter<check_content_type>::JsonFilter() {}
__attribute__((always_inline)) inline bool Contains(std::string_view str, const std::string &val) { return str.find(val) != std::string::npos; }
void JsonFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
__attribute__((always_inline)) inline bool Contains(std::string_view str, const std::string &val)
{
return str.find(val) != std::string::npos;
}
template <bool check_content_type>
void JsonFilter<check_content_type>::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
std::string_view content_type = req->getHeader("content-type");
std::string_view accept_header = req->getHeader("Accept");
if (content_type == "applications/json" && (Contains(accept_header, "*/*") || Contains(accept_header, "application/json")))
if constexpr (check_content_type)
{
fccb();
return;
std::string_view content_type = req->getHeader("content-type");
if (content_type == "application/json" && (Contains(accept_header, "*/*") || Contains(accept_header, "application/json")))
{
fccb();
return;
}
const auto &resp = HttpResponse::newCustomHttpResponse(BankResponse(k406NotAcceptable, "\"Client must Accept and have content-type of JSON\""));
fcb(resp);
}
else
{
if ((Contains(accept_header, "*/*") || Contains(accept_header, "application/json")))
{
fccb();
return;
}
const auto &resp = HttpResponse::newCustomHttpResponse(BankResponse(k406NotAcceptable, "\"Client must Accept JSON\""));
fcb(resp);
}
}
const auto &resp = HttpResponse::newHttpJsonResponse("Client must Accept JSON");
resp->setStatusCode(k406NotAcceptable);
fcb(resp);
}
template class JsonFilter<true>;
template class JsonFilter<false>;

View file

@ -1,57 +1,50 @@
#include "log.h"
void Log::AddTrans(Transaction &&t) noexcept
void Log::AddTrans(const Transaction &t) noexcept
{
log_flag.SetChangesOn();
#if MAX_LOG_SIZE == 1
data = std::move(t);
data = t;
#else
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
for (size_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
data.push_back(t); // In either case we have space under max length, move to new spot
#endif
log_flag.SetChangesOn();
}
const Json::Value &Log::GetLog() noexcept
const std::string &Log::GetLogs() noexcept
{
if (log_flag.GetChangeState()) //if there are changes
{
//re-generate snapshot
Json::Value res;
#if MAX_LOG_SIZE == 1
res[0]["to"] = data.to;
res[0]["from"] = data.from;
#ifdef _USE_32BIT_TIME_T
res[0]["time"] = (Json::UInt)data.time;
#else
res[0]["time"] = (Json::UInt64)data.time;
#endif
#else
for (uint32_t i = data.size(); i > 0; --i)
//({\"amount\":1,\"from\":\"\",\"time\":1625943626,\"to\":\"\"}, + (2*max_name_size)+10+10) * # of logs) + 1
size_t predicted_size = ((58 + (2 * max_name_size)) * data.size()) + 1;
if (log_snapshot.capacity() < predicted_size)
{
res[i - 1]["to"] = data[data.size() - i].to;
res[i - 1]["from"] = data[data.size() - i].from;
res[i - 1]["amount"] = (Json::UInt)data[data.size() - i].amount;
#ifdef _USE_32BIT_TIME_T
res[i - 1]["time"] = (Json::UInt)data[data.size() - i].time;
#else
res[i - 1]["time"] = (Json::UInt64)data[data.size() - i].time;
#endif
log_snapshot.reserve(predicted_size);
}
#endif
log_snapshot = '[';
for (size_t i = 0; i < data.size(); ++i)
{
log_snapshot += "{\"to\":\"";
log_snapshot += data[i].to;
log_snapshot += "\",\"from\":\"";
log_snapshot += data[i].from;
log_snapshot += "\",\"amount\":";
log_snapshot += std::to_string(data[i].amount);
log_snapshot += ",\"time\":";
log_snapshot += std::to_string(data[i].time);
log_snapshot += "},";
}
log_snapshot[log_snapshot.size() - 1] = ']';
log_flag.SetChangesOff();
log_snapshot = res;
}
return log_snapshot;
}
@ -74,9 +67,9 @@ Json::Value Log::Serialize() const
res[i]["from"] = data[i].from;
res[i]["amount"] = (Json::UInt)data[i].amount;
#ifdef _USE_32BIT_TIME_T
res[i]["time"] = (Json::UInt)data[i].time;
res[i]["time"] = (Json::Int)data[i].time;
#else
res[i]["time"] = (Json::UInt64)data[i].time;
res[i]["time"] = (Json::Int64)data[i].time;
#endif
}
#endif

12032
src/simdjson.cpp Normal file

File diff suppressed because it is too large Load diff

45
src/str_intrusion.cpp Normal file
View file

@ -0,0 +1,45 @@
#include "str_intrusion.h"
//this function is horribly jank
template <typename Tag>
struct result
{
typedef typename Tag::type type;
static type ptr;
};
template <typename Tag>
typename result<Tag>::type result<Tag>::ptr;
template <typename Tag, typename Tag::type p>
struct rob : result<Tag>
{
struct filler
{
filler() { result<Tag>::ptr = p; }
};
static filler filler_obj;
};
template <typename Tag, typename Tag::type p>
typename rob<Tag, p>::filler rob<Tag, p>::filler_obj;
struct string_length
{
typedef void (std::string::*type)(size_t);
};
template class rob<string_length, &std::string::_M_length>;
struct string_data
{
typedef void (std::string::*type)(char *);
};
template class rob<string_data, &std::string::_M_data>;
StrFromSV_Wrapper::StrFromSV_Wrapper() noexcept {}
StrFromSV_Wrapper::StrFromSV_Wrapper(std::string_view sv) noexcept
{
(str.*result<string_data>::ptr)((char *)sv.data());
(str.*result<string_length>::ptr)(sv.size());
}
StrFromSV_Wrapper::~StrFromSV_Wrapper() noexcept
{
(str.*result<string_data>::ptr)(nullptr);
(str.*result<string_length>::ptr)(0);
}

View file

@ -1,5 +1,5 @@
#include "transaction.h"
Transaction::Transaction() = default;
Transaction::Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount, time_t time_val) : from(from_str), to(to_str), amount(amount), time(time_val) {}
Transaction::Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount) : from(from_str), to(to_str), amount(amount) { time = std::time(NULL); }
Transaction::Transaction() noexcept {};
Transaction::Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount, time_t time_val) noexcept : from(from_str), to(to_str), amount(amount), time(time_val) {}
Transaction::Transaction(const std::string &from_str, const std::string &to_str, uint32_t amount) noexcept : from(from_str), to(to_str), amount(amount) { time = std::time(NULL); }

View file

@ -27,17 +27,21 @@ User::User(uint32_t init_bal, XXH64_hash_t init_pass, const Json::Value &log_j)
{
if (log_j.size())
{
log.data.reserve(std::min((size_t)PRE_LOG_SIZE * ((log_j.size() / PRE_LOG_SIZE) + 1), (size_t)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(Transaction(
log_j[i]["from"].asCString(),
log_j[i]["to"].asCString(),
log_j[i]["amount"].asUInt(),
#ifdef _USE_32BIT_TIME_T
log_j[i]["time"].asUInt()));
#if MAX_LOG_SIZE == 1
log.data = (
#else
log_j[i]["time"].asUInt64()));
log.data.push_back(
#endif
Transaction(
log_j[i]["from"].asCString(),
log_j[i]["to"].asCString(),
log_j[i]["amount"].asUInt(),
#ifdef _USE_32BIT_TIME_T
log_j[i]["time"].asUInt()));
#else
log_j[i]["time"].asUInt64()));
#endif
}
}

View file

@ -1,36 +1,103 @@
#include "user_filter.h"
UserFilter::UserFilter(Bank &b) : bank(b) {}
template <bool set_body_flag, bool require_admin>
UserFilter<set_body_flag, require_admin>::UserFilter(Bank &b) : bank(b) {}
void UserFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
#define time_func_a(f, a, x) \
{ \
using namespace std::chrono; \
uint32_t timer = 0; \
for (int i = 0; i < x; ++i) \
{ \
auto t1 = high_resolution_clock::now().time_since_epoch(); \
f; \
auto t2 = high_resolution_clock::now().time_since_epoch(); \
a; \
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
} \
std::cout << timer / x << '\n'; \
}
#define time_func(f, x) \
{ \
using namespace std::chrono; \
uint32_t timer = 0; \
for (int i = 0; i < x; ++i) \
{ \
auto t1 = high_resolution_clock::now().time_since_epoch(); \
f; \
auto t2 = high_resolution_clock::now().time_since_epoch(); \
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
} \
std::cout << timer / x << '\n'; \
}
#define Op_a(v, name, x, a) \
{ \
std::cout << name; \
time_func_a(v, a, x); \
}
#define Op(v, name, x) \
{ \
std::cout << name; \
time_func(v, x); \
}
template <bool set_body_flag, bool require_admin>
void UserFilter<set_body_flag, require_admin>::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
std::string_view auth_header = req->getHeader("Authorization");
if (auth_header.size() > 6)
if (auth_header.size() > 6 && auth_header.size() <= ((max_name_size + 256) * 4) / 3) //"Basic " + (username + ':' + password) * 4/3
{
if (auth_header.substr(0, 6) == "Basic ")
{
std::string_view base64_input = auth_header.substr(6);
char base64_result[(base64_input.size() * 3) / 4]; //only alloc
char result_buffer[max_name_size + 256]; //(username + ':' + 255 password)
size_t new_sz;
base64_decode(base64_input.data(), base64_input.size(), base64_result, &new_sz, 0);
base64_decode(base64_input.data(), base64_input.size(), result_buffer, &new_sz, 0);
std::string_view results_view(base64_result, new_sz);
std::string_view results_view(result_buffer, new_sz);
std::size_t middle = results_view.find(':');
if (middle != std::string::npos)
if (middle != std::string::npos && ((new_sz - middle) <= 256))
{
std::string_view username = results_view.substr(0, middle);
std::string_view password = results_view.substr(middle + 1);
if (bank.VerifyPassword(username, password))
StrFromSV_Wrapper username(results_view.substr(0, middle));
if (ValidUsername(username.str)) //check if username is a valid attempt to avoid hashing/grabbing shared lock
{
fccb();
return;
if constexpr (require_admin)
{
if (bank.AdminVerifyAccount(username.str))
{
StrFromSV_Wrapper password(results_view.substr(middle + 1));
if (bank.VerifyPassword(username.str, password.str))
{
fccb();
return;
}
}
}
else
{
StrFromSV_Wrapper password(results_view.substr(middle + 1));
if (bank.VerifyPassword(username.str, results_view.substr(middle + 1)))
{
if constexpr (set_body_flag)
{
req->setParameter("name", username.str);
}
fccb();
return;
}
}
}
}
}
}
const auto &resp = HttpResponse::newHttpJsonResponse("Invalid Credentials");
resp->setStatusCode(k401Unauthorized);
fcb(resp);
}
fcb(HttpResponse::newCustomHttpResponse(BankResponse(k401Unauthorized, "\"Invalid Credentials\"")));
}
template class UserFilter<true, false>; //user default
template class UserFilter<false, false>; //user sparse
template class UserFilter<false, true>; //admin

42
src/xxhash.c Normal file
View file

@ -0,0 +1,42 @@
/*
* xxHash - Extremely Fast Hash algorithm
* Copyright (C) 2012-2020 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
/*
* xxhash.c instantiates functions defined in xxhash.h
*/
#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
#define XXH_IMPLEMENTATION /* access definitions */
#include "xxhash.h"

View file

@ -4,11 +4,7 @@ XXH64_hash_t xxHashStringGen::operator()(const std::string &str) const noexcept
{
return XXH3_64bits(str.data(), str.size());
}
XXH64_hash_t xxHashStringGen::operator()(std::string &&str) const noexcept
XXH64_hash_t xxHashStringGen::operator()(const std::string_view &str) const noexcept
{
return XXH3_64bits(str.data(), str.size());
}
XXH64_hash_t xxHashStringGen::operator()(std::string_view str) const noexcept
{
return XXH3_64bits(str.data(), str.size());
}
}

1
third_party/xxHash vendored

@ -1 +0,0 @@
Subproject commit 0e49217046917180b9118a2d89aceaa76a65cf51