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 .vscode
build build
ccash_config.hpp

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "drogon"] [submodule "drogon"]
path = third_party/drogon path = third_party/drogon
url = https://github.com/an-tao/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"] [submodule "third_party/base64"]
path = third_party/base64 path = third_party/base64
url = https://github.com/aklomp/base64 url = https://github.com/aklomp/base64

View file

@ -14,19 +14,20 @@ find_package(Threads REQUIRED)
add_executable(${PROJECT_NAME} main.cpp ) 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 target_sources(${PROJECT_NAME} PRIVATE
src/json_filter.cpp
src/admin_filter.cpp
src/bank_api.cpp src/bank_api.cpp
src/bank_resp.cpp
src/bank.cpp src/bank.cpp
src/change_flag.cpp src/change_flag.cpp
src/json_filter.cpp
src/log.cpp src/log.cpp
src/simdjson.cpp
src/str_intrusion.cpp
src/transaction.cpp src/transaction.cpp
src/user_filter.cpp src/user_filter.cpp
src/user.cpp src/user.cpp
src/xxhash_str.cpp src/xxhash_str.cpp
src/xxhash.c
) )
if(DEFINED USER_SAVE_LOC) if(DEFINED USER_SAVE_LOC)
@ -47,12 +48,6 @@ else()
set(MAX_LOG_SIZE_VAL 100) set(MAX_LOG_SIZE_VAL 100)
endif() 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) if(DEFINED CONSERVATIVE_DISK_SAVE)
set(CONSERVATIVE_DISK_SAVE_VAL ${CONSERVATIVE_DISK_SAVE}) set(CONSERVATIVE_DISK_SAVE_VAL ${CONSERVATIVE_DISK_SAVE})
else() else()
@ -83,7 +78,6 @@ target_include_directories(${PROJECT_NAME} PUBLIC third_party/base64/include)
add_subdirectory(third_party/drogon) add_subdirectory(third_party/drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE drogon) target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_THREAD_LIBS_INIT} ) 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 #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) 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) * [problem/solution](docs/idea.md)
* [contributors](docs/contributors.md)
* connected services * connected services
* how to contribute * how to contribute
* [explanation](docs/connected_services/how_to/explanation.md) * [explanation](docs/connected_services/how_to/explanation.md)
* [language APIs](docs/connected_services/how_to/APIs.md) * [language APIs](docs/connected_services/how_to/APIs.md)
* [API endpoints](docs/connected_services/how_to/endpoints.md) * [API endpoints](docs/connected_services/how_to/endpoints.md)
* [existing services](docs/connected_services/existing_services.md) * [existing services](docs/connected_services/existing_services.md)
* features * features
* [user side](docs/features/user_side.md) * [user side](docs/features/user_side.md)
* [implementation](docs/features/implementation.md) * [implementation](docs/features/implementation.md)
* [building](docs/building.md) * [building](docs/building.md)
* [FAQ](docs/FAQ.md) * [FAQ](docs/FAQ.md)
* [contributors](docs/contributors.md)
* [Patreon](https://www.patreon.com/twoxx) if you wanna support this and/or future projects. * [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 <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <random> #include <random>
#include "xxhash_str.h"
#include "bank.h" #include "bank.h"
#include <signal.h> #include <signal.h>
@ -62,12 +63,17 @@ static Bank bank;
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
bank.AddUser("twix", 0, "root"); bank.AddUser("twix", 0, "root");
bank.AddUser("jolly", 0, "root"); bank.AddUser("jolly", 0, "root");
bank.admin_account = "twix"; 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_a(bank.AddUser("", 0, ""), "add user: ", 1000000, bank.DelUser(""));
Op(bank.AddBal("twix", 1), "give bal: ", 1000000); Op(bank.ImpactBal("twix", 1), "impact bal: ", 1000000);
Op(bank.SubBal("twix", 1), "give bal: ", 1000000);
Op(bank.SetBal("twix", 1000000), "set bal: ", 1000000); Op(bank.SetBal("twix", 1000000), "set bal: ", 1000000);
Op(bank.SendFunds("twix", "jolly", 1), "send funds: ", 1000000); Op(bank.SendFunds("twix", "jolly", 1), "send funds: ", 1000000);
Op(bank.SendFunds("twix", "twix", 1), "invalid 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.GetBal("twix"), "get bal: ", 1000000);
Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000); Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000);
Op(bank.ChangePassword("twix", "root"), "change pass: ", 1000000); Op(bank.ChangePassword("twix", "root"), "change pass: ", 1000000);
Op(bank.GetLogs("twix"), "get logs: ", 10000); #if MAX_LOG_SIZE > 0
Op(bank.Save(), "saving: ", 1); Op(bank.GetLogs("twix"), "get logs: ", 1000000);
#if CONSERVATIVE_DISK_SAVE
Op(bank.GetChangeState(), "change flag: ", 10000);
#endif #endif
#if CONSERVATIVE_DISK_SAVE
Op(bank.GetChangeState(), "change flag: ", 1000000);
#endif
Op(bank.Save(), "saving: ", 1);
//GetBal scalining test //GetBal scalining test
// std::default_random_engine generator; // std::default_random_engine generator;
@ -93,13 +101,13 @@ int main(int argc, char **argv)
// for (size_t i = 0; i < 10000000; ++i) // 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) // 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); // Op(bank.GetBal(u), std::to_string(i) + ", ", 100000);
// } // }
// } // }
return 0; return 0;
} }

View file

@ -1,8 +1,7 @@
#pragma once #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 MAX_LOG_SIZE @MAX_LOG_SIZE_VAL@
#define PRE_LOG_SIZE @PRE_LOG_SIZE_VAL@
//default to minecraft usernames //default to minecraft usernames
constexpr unsigned min_name_size = 3; constexpr unsigned min_name_size = 3;

View file

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

View file

@ -1,14 +1,43 @@
# Building # 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 ## Drogon Depedencies
### Linux ### Linux
#### Debian #### 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 #### 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 ### MacOS
`brew install jsoncpp ossp-uuid openssl zlib` ```
brew install jsoncpp ossp-uuid openssl zlib
```
### Docker ### Docker
Docker Package can be found [here](https://github.com/EntireTwix/CCash/packages/851105) 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 CCash
cd third_party/base64 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 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 mkdir build
cd build cd build
``` ```
### CMake Flags ### CMake Flags
there are multiple flags responsible configuring CCash: there are multiple flags responsible configuring CCash:
| name | default | description | pros | cons | | name | default | description | pros | cons |
| :--------------------- | :--------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------- | | :--------------------- | :--------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------- |
| USER_SAVE_LOC | "../users.json" | where the users are saved | `N/A` | `N/A` | | 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` | | 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 | | 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 |
| 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 |
| 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 |
| 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 simply running
@ -48,8 +76,12 @@ cmake -DMULTI_THREADING=false ..
``` ```
with `-D` with `-D`
### Lastly ### Finishing building
lastly type in
``` ```
cmake <flags of your choice or none> .. cmake <flags of your choice or none> ..
make -j<threads> 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: 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) ![image](connected_a.png)

View file

@ -1,7 +1,8 @@
# Contributors # Contributors
| name | work | | name | work |
| :------------------------------------------ | ----------------------- | | :------------------------------------------ | -------------------------- |
| Caesay | Restful API suggestions | | Caesay | Restful API suggestions |
| [Luke](https://github.com/LukeeeeBennett) | Docker package | | [Luke](https://github.com/LukeeeeBennett) | Docker package |
| [React](https://github.com/Reactified) | logo | | [React](https://github.com/Reactified) | logo |
| [Doggo](https://github.com/FearlessDoggo21) | Logs optimized | | [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. 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: 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. 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 <iostream> //temporary
#include <fstream> #include <fstream>
#include <shared_mutex> #include <shared_mutex>
#include <drogon/HttpTypes.h>
#include <parallel-hashmap/parallel_hashmap/phmap.h> #include <parallel-hashmap/parallel_hashmap/phmap.h>
#include "bank_resp.h"
#include "user.h" #include "user.h"
#if (CONSERVATIVE_DISK_SAVE && MAX_LOG_SIZE < 0) && !MULTI_THREADED #if (CONSERVATIVE_DISK_SAVE && MAX_LOG_SIZE < 0) && !MULTI_THREADED
#include "change_flag.h" #include "change_flag.h"
#endif #endif
using BankResponse = std::pair<drogon::HttpStatusCode, Json::Value>; bool ValidUsername(const std::string &name) noexcept;
class Bank class Bank
{ {
@ -30,7 +30,7 @@ class Bank
private: private:
#if CONSERVATIVE_DISK_SAVE #if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED #if MULTI_THREADED
ChangeFlag save_flag; ChangeFlag<false> save_flag;
#else #else
bool save_flag = false; bool save_flag = false;
#endif #endif
@ -42,27 +42,29 @@ public:
std::string admin_account; std::string admin_account;
size_t NumOfUsers() const noexcept; size_t NumOfUsers() const noexcept;
uint64_t NumOfLogs() const noexcept; size_t NumOfLogs() const noexcept;
size_t SumBal() const noexcept;
#if CONSERVATIVE_DISK_SAVE #if CONSERVATIVE_DISK_SAVE
bool GetChangeState() const noexcept; bool GetChangeState() const noexcept;
#endif #endif
BankResponse GetBal(const std::string &name) const noexcept; BankResponse GetBal(const std::string &name) const noexcept;
#if MAX_LOG_SIZE > 0
BankResponse GetLogs(const std::string &name) noexcept; BankResponse GetLogs(const std::string &name) noexcept;
#endif
BankResponse SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount) noexcept; 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 SetBal(const std::string &name, uint32_t amount) noexcept;
BankResponse AddBal(const std::string &name, uint32_t amount) noexcept; BankResponse ImpactBal(const std::string &name, int64_t amount) noexcept;
BankResponse SubBal(const std::string &name, uint32_t amount) noexcept;
bool Contains(const std::string &name) const 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; BankResponse DelUser(const std::string &name) noexcept;
void Save(); const char *Save();
void Load(); void Load();
}; };

View file

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

View file

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

View file

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

View file

@ -1,17 +1,19 @@
#pragma once #pragma once
#include <drogon/HttpFilter.h> #include <drogon/HttpFilter.h>
#include <libbase64.h> #include <libbase64.h>
#include "str_intrusion.h"
#include "bank.h" #include "bank.h"
using namespace drogon; 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: private:
Bank &bank; Bank &bank;
public: public:
UserFilter(Bank &); UserFilter(Bank &b);
virtual void doFilter(const HttpRequestPtr &, virtual void doFilter(const HttpRequestPtr &,
FilterCallback &&, 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 #pragma once
#include <string> #include <string>
#include <xxhash.h> #include "xxhash.h"
struct xxHashStringGen struct xxHashStringGen
{ {
XXH64_hash_t operator()(const std::string &str) const noexcept; XXH64_hash_t operator()(const std::string &str) const noexcept;
XXH64_hash_t operator()(std::string &&str) const noexcept; XXH64_hash_t operator()(const std::string_view &str) const noexcept;
XXH64_hash_t operator()(std::string_view str) const noexcept;
}; };

135
main.cpp
View file

@ -21,98 +21,91 @@ static Bank bank;
void SaveSig(int s) void SaveSig(int s)
{ {
std::cout << "\nSaving on close...\n"; std::cout << "\nSaving on close...\n"
bank.Save(); << bank.Save();
exit(1); exit(1);
} }
int main(int argc, char **argv) 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 (argc != 3)
} {
if (geteuid() != 0) std::cerr << "Usage: sudo ./bank <admin account> <saving frequency in minutes>\n";
{ return 0;
std::cerr << "ERROR: CCash MUST be ran as root\n"; }
return 0; if (geteuid() != 0)
} {
std::cout std::cerr << "ERROR: CCash MUST be ran as root\n";
<< "\nAVX : " << (__builtin_cpu_supports("avx") ? "enabled" : "disabled") return 0;
<< "\nAVX 2 : " << (__builtin_cpu_supports("avx2") ? "enabled" : "disabled") }
<< "\nSSE 2 : " << (__builtin_cpu_supports("sse2") ? "enabled" : "disabled") std::cout
<< "\nSSE 3 : " << (__builtin_cpu_supports("sse3") ? "enabled" : "disabled") << "\nAPI Version : " << API_VERSION
<< "\nSSE 4.1 : " << (__builtin_cpu_supports("sse4.1") ? "enabled" : "disabled") << "\n\nAVX : " << (__builtin_cpu_supports("avx") ? "enabled" : "disabled")
<< "\nSSE 4.2 : " << (__builtin_cpu_supports("sse4.2") ? "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 #if MULTI_THREADED
<< "\n\nThreads : " << get_nprocs() + 1 << "\n\nThreads : " << get_nprocs() + 1
<< "\nMulti threading : enabled"; << "\nMulti threading : enabled";
#else #else
<< "\n\nThreads : " << 2 << "\n\nThreads : " << 2
<< "\nMulti threading : disabled"; << "\nMulti threading : disabled";
#endif #endif
//Loading users from users.json //Loading users from users.json
bank.Load(); 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" //Sig handling
<< "\nLoaded " << bank.NumOfLogs() << " Logs" struct sigaction sigIntHandler;
<< std::endl; //flushing before EventLoop
//Sig handling sigIntHandler.sa_handler = SaveSig;
struct sigaction sigIntHandler; sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigIntHandler.sa_handler = SaveSig; sigaction(SIGINT, &sigIntHandler, NULL);
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL); //Admin account
bank.admin_account = argv[1];
//Admin account //Auto Saving
bank.admin_account = argv[1]; 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
//Auto Saving {
const unsigned long saving_freq = std::stoul(std::string(argv[2])); std::thread([saving_freq]() {
if (saving_freq) //if saving frequency is 0 then auto saving is turned off while (1)
{
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())
{ {
std::cout << " to disk...\n"; std::this_thread::sleep_for(std::chrono::minutes(saving_freq));
bank.Save(); std::cout << "Saving " << std::time(0) << "...\n"
<< bank.Save();
} }
else })
{ .detach();
std::cout << " no changes...\n"; }
} } //destroying setup variables
} static auto API = std::make_shared<api>(bank);
}) static auto user_filter_default = std::make_shared<UserFilter<true, false>>(bank);
.detach(); 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() app()
.loadConfigFile(config_location) .loadConfigFile(config_location)
.registerFilter(user_filter) .registerFilter(user_filter_default)
.registerFilter(user_filter_sparse)
.registerFilter(admin_filter) .registerFilter(admin_filter)
.registerFilter(json_resp_and_req_filter)
.registerFilter(json_resp_filter)
.registerController(API) .registerController(API)
#if MULTI_THREADED #if MULTI_THREADED
.setThreadNum(get_nprocs()) .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; 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) 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) for (const char &c : name)
{ {
if (!(std::isalpha(c) || std::isdigit(c) || c == '_')) if (!((c >= 97 && c <= 122) || std::isdigit(c) || c == '_'))
{ {
return false; 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(); } size_t Bank::NumOfUsers() const noexcept { return users.size(); }
//NOT THREAD SAFE //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) for (const auto &u : users)
{ {
#if MAX_LOG_SIZE == 1
if (u.second.log.data.amount)
{
++res;
}
#else
res += u.second.log.data.size(); 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; return res;
} }
@ -45,77 +65,83 @@ bool Bank::GetChangeState() const noexcept
BankResponse Bank::GetBal(const std::string &name) const noexcept BankResponse Bank::GetBal(const std::string &name) const noexcept
{ {
uint64_t res = 0; uint32_t res = 0;
users.if_contains(name, [&res](const User &u) { res = u.balance + 1; }); if (!users.if_contains(name, [&res](const User &u) { res = u.balance; }))
return res ? BankResponse(k200OK, res - 1) : BankResponse(k404NotFound, "User not found"); {
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 Bank::GetLogs(const std::string &name) noexcept
{ {
BankResponse res; BankResponse res;
#if MAX_LOG_SIZE > 0 if (!users.modify_if(name, [&res](User &u) { res = {k200OK, u.log.GetLogs()}; }))
if (!users.modify_if(name, [&res](User &u) { res = {k200OK, u.log.GetLog()}; }))
{ {
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 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 //cant send money to self, from self or amount is 0
if (a_name == b_name) if (a_name == b_name)
{ {
return {k400BadRequest, "Sender and Reciever names cannot match"}; return {k400BadRequest, "\"Sender and Reciever names cannot match\""};
} }
//cant send 0 //cant send 0
if (!amount) 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 //as first modify_if checks a_name and grabs unique lock
if (!Contains(b_name)) if (!Contains(b_name))
{ {
return {k404NotFound, "Reciever does not exist"}; return {k404NotFound, "\"Reciever does not exist\""};
} }
BankResponse state; BankResponse state;
std::shared_lock<std::shared_mutex> lock{save_lock}; //about 10% of this function's cost std::shared_lock<std::shared_mutex> lock{save_lock}; //about 10% of this function's cost
#if MAX_LOG_SIZE > 0 #if MAX_LOG_SIZE > 0
Transaction temp(a_name, b_name, amount); static thread_local 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) {
#endif #endif
if (!users.modify_if(a_name, [&state, amount](User &a) {
//if A can afford it //if A can afford it
if (a.balance < amount) if (a.balance < amount)
{ {
state = {k400BadRequest, "Sender has insufficient funds"}; state = {k400BadRequest, "\"Sender has insufficient funds\""};
} }
else else
{ {
a.balance -= amount; a.balance -= amount;
#if MAX_LOG_SIZE > 0 #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 #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 (state.first == k200OK)
{ {
#if MAX_LOG_SIZE > 0 #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.balance += amount;
b.log.AddTrans(std::move(temp)); b.log.AddTrans(temp);
}); //about 40% of this function's cost }); //about 40% of this function's cost
#else #else
users.modify_if(b_name, [amount](User &b) { b.balance += amount; }); users.modify_if(b_name, [amount](User &b) { b.balance += amount; });
#endif #endif
#if CONSERVATIVE_DISK_SAVE #if CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED #if MULTI_THREADED
save_flag.SetChangesOn(); //about 5% of this function's cost 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; 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; 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; 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 CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED #if MULTI_THREADED
save_flag.SetChangesOn(); save_flag.SetChangesOn();
@ -143,10 +168,15 @@ void Bank::ChangePassword(const std::string &name, std::string &&new_pass) noexc
save_flag = true; save_flag = true;
#endif #endif
#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 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 CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED #if MULTI_THREADED
@ -155,20 +185,17 @@ BankResponse Bank::SetBal(const std::string &name, uint32_t amount) noexcept
save_flag = true; save_flag = true;
#endif #endif
#endif #endif
return {k200OK, "Balance set!"}; return {k204NoContent, std::nullopt}; //may return new balance
}
else
{
return {k404NotFound, "User not found"};
} }
} }
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 CONSERVATIVE_DISK_SAVE
#if MULTI_THREADED #if MULTI_THREADED
@ -177,49 +204,26 @@ BankResponse Bank::AddBal(const std::string &name, uint32_t amount) noexcept
save_flag = true; save_flag = true;
#endif #endif
#endif #endif
return {k200OK, "Balance added!"}; return {k200OK, std::to_string(balance)}; //may return new balance
} }
else else
{ {
return {k404NotFound, "User not found"}; 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"};
} }
} }
bool Bank::Contains(const std::string &name) const noexcept bool Bank::Contains(const std::string &name) const noexcept
{ {
return users.contains(name); return users.contains(name);
} }
bool Bank::AdminVerifyAccount(std::string_view name) noexcept bool Bank::AdminVerifyAccount(const std::string &name) noexcept
{ {
return (name == admin_account); return (name == admin_account);
} }
BankResponse Bank::AddUser(const std::string &name, uint32_t init_bal, std::string &&init_pass) noexcept
BankResponse Bank::AddUser(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}; std::shared_lock<std::shared_mutex> lock{save_lock};
if (users.try_emplace_l( if (users.try_emplace_l(
@ -232,11 +236,11 @@ BankResponse Bank::AddUser(std::string &&name, uint32_t init_bal, std::string &&
save_flag = true; save_flag = true;
#endif #endif
#endif #endif
return {k200OK, "User added!"}; return {k204NoContent, std::nullopt};
} }
else else
{ {
return {k409Conflict, "User already exists"}; return {k409Conflict, "\"User already exists\""};
} }
} }
BankResponse Bank::DelUser(const std::string &name) noexcept 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}; std::shared_lock<std::shared_mutex> lock{save_lock};
#if RETURN_ON_DEL #if RETURN_ON_DEL
uint32_t bal; uint32_t bal;
if (users.if_contains(name, [this, &bal](const User &u) { if (users.if_contains(name, [&bal](const User &u) { bal = u.balance; }) &&
bal = u.balance; bal)
}))
{ {
users.modify_if(return_account, [ this, bal ](User & u)) users.modify_if(return_account, [bal](User & u))
{ {
u.balance += bal; u.balance += bal;
} }
@ -263,20 +266,20 @@ BankResponse Bank::DelUser(const std::string &name) noexcept
save_flag = true; save_flag = true;
#endif #endif
#endif #endif
return BankResponse(k200OK, "User deleted!"); return {k204NoContent, std::nullopt};
} }
else else
{ {
return BankResponse(k404NotFound, "User not found"); return {k404NotFound, "\"User not found\""};
} }
} }
void Bank::Save() const char *Bank::Save()
{ {
#if CONSERVATIVE_DISK_SAVE #if CONSERVATIVE_DISK_SAVE
if (GetChangeState()) if (GetChangeState())
{ {
#endif #endif
Json::Value temp; static thread_local Json::Value temp;
//loading info into json temp //loading info into json temp
{ {
@ -284,7 +287,7 @@ void Bank::Save()
for (const auto &u : users) for (const auto &u : users)
{ {
//we know it contains this key but we call this func to grab mutex //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()) if (temp.isNull())
@ -293,9 +296,9 @@ void Bank::Save()
} }
else else
{ {
std::ofstream user_save(users_location); static thread_local std::ofstream user_save(users_location);
Json::StreamWriterBuilder builder; static thread_local Json::StreamWriterBuilder builder;
const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); static thread_local const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(temp, &user_save); writer->write(temp, &user_save);
user_save.close(); user_save.close();
} }
@ -305,6 +308,11 @@ void Bank::Save()
#else #else
save_flag = true; save_flag = true;
#endif #endif
return " to disk...\n";
}
else
{
return " no changes...\n";
} }
#endif #endif
} }
@ -312,12 +320,12 @@ void Bank::Save()
//NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS //NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS
void Bank::Load() void Bank::Load()
{ {
Json::CharReaderBuilder builder; static thread_local Json::CharReaderBuilder builder;
Json::Value temp; static thread_local Json::Value temp;
std::ifstream user_save(users_location); static thread_local std::ifstream user_save(users_location);
builder["collectComments"] = true; builder["collectComments"] = true;
JSONCPP_STRING errs; static thread_local JSONCPP_STRING errs;
if (!parseFromStream(builder, user_save, &temp, &errs)) if (!parseFromStream(builder, user_save, &temp, &errs))
{ {
std::cerr << errs << '\n'; std::cerr << errs << '\n';

View file

@ -1,54 +1,37 @@
#include "bank_api.h" #include "bank_api.h"
// const auto data(R); //all my homies hate jsoncpp
// auto res = std::make_shared<HttpResponseImpl>(data.first, CT_APPLICATION_JSON);
// res->setJsonObject(JsonCast(std::move(data.second)));
// doResponseCreateAdvices(res);
// callback(res);
#define CACHE_FOREVER resp->setExpiredTime(0); #define CACHE_FOREVER resp->setExpiredTime(0)
#define GEN_BODY \ #define CORS resp->addHeader("Access-Control-Allow-Origin", "*")
const auto temp_req = req->getJsonObject(); \
const auto body = temp_req ? *temp_req : Json::Value();
#define RESPONSE_PARSE(R) \ #define GEN_BODY \
const auto r(R); \ static thread_local const auto temp_req = req->getJsonObject(); \
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(std::move(r.second))); \ static thread_local const auto body = temp_req ? *temp_req : Json::Value()
resp->setStatusCode(r.first); \
callback(resp);
#define RESPOND_TRUE \ static thread_local ondemand::parser parser;
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(true)); \ #define SIMD_JSON_GEN \
CACHE_FOREVER \ static thread_local simdjson::padded_string input(req->getBody()); \
callback(resp); 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> #define RESPOND_TRUE \
constexpr Json::Value JsonCast(T &&val) 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 #if API_VERSION >= 1
//Usage //Usage
@ -56,7 +39,7 @@ void api::GetBal(req_args, const std::string &name) const
{ {
RESPONSE_PARSE(bank.GetBal(name)); RESPONSE_PARSE(bank.GetBal(name));
} }
void api::GetLog(req_args) void api::GetLogs(req_args)
{ {
if constexpr (MAX_LOG_SIZE > 0) if constexpr (MAX_LOG_SIZE > 0)
{ {
@ -64,99 +47,196 @@ void api::GetLog(req_args)
} }
else else
{ {
auto resp = HttpResponse::newHttpJsonResponse("Logs are Disabled"); static thread_local auto resp = HttpResponse::newCustomHttpResponse(BankResponse(k404NotFound, "\"Logs are Disabled\""));
resp->setStatusCode(k404NotFound); CORS;
CACHE_FOREVER CACHE_FOREVER;
callback(resp); callback(resp);
} }
} }
void api::SendFunds(req_args) const void api::SendFunds(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
RESPONSE_PARSE(bank.SendFunds(NAME_PARAM, body["to"].asCString(), body["amount"].asUInt())); 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 //Meta Usage
void api::ChangePassword(req_args) const void api::ChangePassword(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
bank.ChangePassword(NAME_PARAM, std::move(body["new_pass"].asCString())); auto pass = doc.find_field("pass").get_string();
RESPOND_TRUE 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 void api::AdminChangePassword(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
bank.ChangePassword(body["name"].asCString(), std::move(body["new_pass"].asCString())); auto name = doc.find_field("name").get_string();
RESPOND_TRUE 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 void api::SetBal(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
RESPONSE_PARSE(bank.SetBal(body["name"].asCString(), body["amount"].asUInt())); 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 SIMD_JSON_GEN;
RESPONSE_PARSE(bank.AddBal(body["name"].asCString(), body["amount"].asUInt())); auto name = doc.find_field("name").get_string();
} auto amount = doc.find_field("amount").get_int64();
void api::SubBal(req_args) const BankResponse res;
{ if (name.error() || amount.error())
GEN_BODY {
RESPONSE_PARSE(bank.AddBal(body["name"].asCString(), body["amount"].asUInt())); 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 //System Usage
void api::Help(req_args) const 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; CACHE_FOREVER;
callback(resp); callback(resp);
} }
void api::Ping(req_args) const
{
RESPOND_TRUE
}
void api::Close(req_args) const void api::Close(req_args) const
{ {
bank.Save(); bank.Save();
RESPOND_TRUE; //filter handles admin creds
app().quit(); app().quit();
RESPOND_TRUE //filter handles admin creds
} }
void api::Contains(req_args, const std::string &name) const void api::Contains(req_args, const std::string &name) const
{ {
auto resp = HttpResponse::newHttpJsonResponse(JsonCast(bank.Contains(name))); RESPONSE_PARSE(BankResponse(k200OK, bank.Contains(name) ? "true" : "false"));
callback(resp);
} }
void api::AdminVerifyAccount(req_args) const 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; CACHE_FOREVER;
callback(resp); callback(resp);
} }
void api::AddUser(req_args) const void api::AddUser(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
RESPONSE_PARSE(bank.AddUser(body["name"].asCString(), 0, body["pass"].asCString())) 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 void api::AdminAddUser(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
RESPONSE_PARSE(bank.AddUser(body["name"].asCString(), body["balance"].asUInt(), body["pass"].asCString())) 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 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 void api::AdminDelUser(req_args) const
{ {
GEN_BODY SIMD_JSON_GEN;
RESPONSE_PARSE(bank.DelUser(body["name"].asCString())) 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 #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" #include "change_flag.h"
ChangeFlag::ChangeFlag() noexcept {} template <bool init>
ChangeFlag<init>::ChangeFlag() noexcept {}
ChangeFlag::ChangeFlag(ChangeFlag &&f) 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); 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); 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); 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" #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; } __attribute__((always_inline)) inline bool Contains(std::string_view str, const std::string &val)
{
void JsonFilter::doFilter(const HttpRequestPtr &req, return str.find(val) != std::string::npos;
FilterCallback &&fcb, }
FilterChainCallback &&fccb)
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"); std::string_view accept_header = req->getHeader("Accept");
if constexpr (check_content_type)
if (content_type == "applications/json" && (Contains(accept_header, "*/*") || Contains(accept_header, "application/json")))
{ {
fccb(); std::string_view content_type = req->getHeader("content-type");
return; 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"); template class JsonFilter<true>;
resp->setStatusCode(k406NotAcceptable); template class JsonFilter<false>;
fcb(resp);
}

View file

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

View file

@ -1,36 +1,103 @@
#include "user_filter.h" #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, #define time_func_a(f, a, x) \
FilterCallback &&fcb, { \
FilterChainCallback &&fccb) 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"); 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 ") if (auth_header.substr(0, 6) == "Basic ")
{ {
std::string_view base64_input = auth_header.substr(6); 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; 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(':'); 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); StrFromSV_Wrapper username(results_view.substr(0, middle));
std::string_view password = results_view.substr(middle + 1); if (ValidUsername(username.str)) //check if username is a valid attempt to avoid hashing/grabbing shared lock
if (bank.VerifyPassword(username, password))
{ {
fccb(); if constexpr (require_admin)
return; {
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"); fcb(HttpResponse::newCustomHttpResponse(BankResponse(k401Unauthorized, "\"Invalid Credentials\"")));
resp->setStatusCode(k401Unauthorized); }
fcb(resp);
} 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()); 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()); 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